﻿///////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2023, ООО 1С-Софт
// Все права защищены. Эта программа и сопроводительные материалы предоставляются 
// в соответствии с условиями лицензии Attribution 4.0 International (CC BY 4.0)
// Текст лицензии доступен по ссылке:
// https://creativecommons.org/licenses/by/4.0/legalcode
///////////////////////////////////////////////////////////////////////////////////////////////////////

#Область ПрограммныйИнтерфейс

// АПК:142-выкл - Необязательных параметров более 3 для поддержки разных прикладных функций.
// АПК:134-выкл - Количество параметров более 7 для поддержки разных прикладных функций.

// Запустить выполнение функции в фоновом задании, если это возможно.
// Вызываемая функция может быть с произвольным числом параметров, но не более 7.
// Значения передаваемых параметров функции, а также возвращаемое значение должны быть сериализуемыми.
// Параметры функции не должны быть возвращаемыми.
//
// Не следует использовать эту функцию, если необходимо безусловно запускать фоновое задание.
// Рекомендуется применять совместно с функцией ДлительныеОперацииКлиент.ОжидатьЗавершение,
// если невозможно, тогда для проверки завершения операции использовать функцию ЗаданиеВыполнено.
//
// Параметры:
//  ПараметрыВыполнения - ФормаКлиентскогоПриложения - форма, из которой выполняется вызов;
//                      - УникальныйИдентификатор - идентификатор формы, из которой выполняется вызов;
//                      - Структура - см. ПараметрыВыполненияФункции
//  ИмяФункции - Строка - имя экспортной функции общего модуля, модуля менеджера объекта 
//                        или модуля обработки, которую необходимо выполнить в фоне.
//                        Например, "МойОбщийМодуль.МояПроцедура", "Отчет.ЗагруженныеДанные.Сформировать"
//                        или "Обработка.ЗагрузкаДанных.МодульОбъекта.Загрузить". 
//
//  Параметр1 - Произвольный - произвольные параметры вызова функции. Количество параметров может быть от 0 до 7.
//  Параметр2 - Произвольный
//  Параметр3 - Произвольный
//  Параметр4 - Произвольный
//  Параметр5 - Произвольный
//  Параметр6 - Произвольный
//  Параметр7 - Произвольный
//
// Возвращаемое значение:
//  Структура: 
//   * Статус               - Строка - "Выполняется", если задание еще не завершилось;
//                                     "Выполнено", если задание было успешно выполнено;
//                                     "Ошибка", если задание завершено с ошибкой;
//                                     "Отменено", если задание отменено пользователем или администратором.
//   * ИдентификаторЗадания - УникальныйИдентификатор - если Статус = "Выполняется", то содержит 
//                                     идентификатор запущенного фонового задания.
//                          - Неопределено - если Статус <> "Выполняется" и фоновое задание не запускалось.
//   * АдресРезультата       - Строка - адрес временного хранилища, в которое будет
//                                      помещен результат работы функции.
//   * КраткоеПредставлениеОшибки   - Строка - краткая информация об исключении, если Статус = "Ошибка".
//   * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
//   * Сообщения - ФиксированныйМассив - если Статус <> "Выполняется", то массив объектов СообщениеПользователю,
//                                      которые были сформированы в фоновом задании.
//
// Пример:
//  В общем виде процесс запуска и обработки результата длительной операции в модуле формы выглядит следующим образом:
//
//   1) Функция, которая будет исполняться в фоне, располагается в модуле менеджера объекта или в серверном общем модуле:
//    Функция РассчитатьЗначение(Знач МойПараметр1, Знач МойПараметр2) Экспорт
//     ...
//     Возврат Результат;
//    КонецФункции
//
//   2) Запуск операции на сервере и подключение обработчика ожидания:
//    &НаКлиенте
//    Процедура РассчитатьЗначение()
//     ДлительнаяОперация = НачатьВыполнениеНаСервере();
//     ОповещениеОЗавершении = Новый ОписаниеОповещения("ОбработатьРезультат", ЭтотОбъект);
//     ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
//     ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, ОповещениеОЗавершении, ПараметрыОжидания);
//    КонецПроцедуры
//
//    &НаСервере
//    Функция НачатьВыполнениеНаСервере()
//     ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияФункции(УникальныйИдентификатор);
//     Возврат ДлительныеОперации.ВыполнитьФункцию(ПараметрыВыполнения, "Обработка.МояОбработка.РассчитатьЗначение", 
//      МойПараметр1, МойПараметр2);
//    КонецФункции
//    
//   3) Обработка результата длительной операции:
//    &НаКлиенте
//    Процедура ОбработатьРезультат(Результат, ДополнительныеПараметры) Экспорт
//     Если Результат = Неопределено Тогда
//      Возврат;
//     КонецЕсли;
//     ВывестиРезультат(Результат.АдресРезультата);
//    КонецПроцедуры 
//  
Функция ВыполнитьФункцию(Знач ПараметрыВыполнения, ИмяФункции, Знач Параметр1 = Неопределено,
	Знач Параметр2 = Неопределено, Знач Параметр3 = Неопределено, Знач Параметр4 = Неопределено,
	Знач Параметр5 = Неопределено, Знач Параметр6 = Неопределено, Знач Параметр7 = Неопределено) Экспорт
	
	ПараметрыВызова = СписокПараметров(Параметр1, Параметр2, Параметр3, Параметр4,
		Параметр5, Параметр6, Параметр7);
	
	ПараметрыВыполнения = ПодготовитьПараметрыВыполнения(ПараметрыВыполнения, Истина);
	
	Возврат ВыполнитьВФоне(ИмяФункции, ПараметрыВызова, ПараметрыВыполнения);
	
КонецФункции

// АПК:141-выкл - Первый необязательный параметр перед обязательным выбран
// для сохранения порядка параметров как в функции ВыполнитьФункцию.

// Запустить выполнение процедуры в фоновом задании, если это возможно.
// Вызываемая процедура может быть с произвольным числом параметров, но не более 7.
// Значения передаваемых параметров процедуры, а также возвращаемое значение должны быть сериализуемыми.
// Параметры процедуры не должны быть возвращаемыми.
//
// Не следует использовать эту функцию, если необходимо безусловно запускать фоновое задание.
// Рекомендуется применять совместно с функцией ДлительныеОперацииКлиент.ОжидатьЗавершение,
// если невозможно, тогда для проверки завершения операции использовать функцию ЗаданиеВыполнено.
//
// Параметры:
//
//  ПараметрыВыполнения - см. ДлительныеОперации.ПараметрыВыполненияПроцедуры
//
//  ИмяПроцедуры - Строка - имя экспортной процедуры общего модуля, модуля менеджера объекта 
//                          или модуля обработки, которую необходимо выполнить в фоне.
//                          Например, "МойОбщийМодуль.МояПроцедура", "Отчет.ЗагруженныеДанные.Сформировать"
//                          или "Обработка.ЗагрузкаДанных.МодульОбъекта.Загрузить". 
//
//  Параметр1 - Произвольный - произвольные параметры вызова процедуры. Количество параметров может быть от 0 до 7.
//  Параметр2 - Произвольный
//  Параметр3 - Произвольный
//  Параметр4 - Произвольный
//  Параметр5 - Произвольный
//  Параметр6 - Произвольный
//  Параметр7 - Произвольный
//
// Возвращаемое значение:
//  Структура - параметры выполнения задания: 
//   * Статус               - Строка - "Выполняется", если задание еще не завершилось;
//                                     "Выполнено", если задание было успешно выполнено;
//                                     "Ошибка", если задание завершено с ошибкой;
//                                     "Отменено", если задание отменено пользователем или администратором.
//   * ИдентификаторЗадания - УникальныйИдентификатор - если Статус = "Выполняется", то содержит 
//                                     идентификатор запущенного фонового задания.
//                          - Неопределено - если Статус <> "Выполняется" и фоновое задание не запускалось.
//   * КраткоеПредставлениеОшибки   - Строка - краткая информация об исключении, если Статус = "Ошибка".
//   * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
//   * Сообщения - ФиксированныйМассив - если Статус <> "Выполняется", то массив объектов СообщениеПользователю,
//                                      которые были сформированы в фоновом задании.
//
// Пример:
//  В общем виде процесс запуска и обработки результата длительной операции в модуле формы выглядит следующим образом:
//
//   1) Процедура, которая будет исполняться в фоне, располагается в модуле менеджера объекта или в серверном общем модуле:
//    Процедура ВыполнитьРасчет(Знач МойПараметр1, Знач МойПараметр2) Экспорт
//     ...
//    КонецПроцедуры
//
//   2) Запуск операции на сервере и подключение обработчика ожидания (при необходимости):
//    &НаКлиенте
//    Процедура ВыполнитьРасчет()
//     ДлительнаяОперация = НачатьВыполнениеНаСервере();
//     ОповещениеОЗавершении = Новый ОписаниеОповещения("ОбработатьРезультат", ЭтотОбъект);
//     ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
//     ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, ОповещениеОЗавершении, ПараметрыОжидания);
//    КонецПроцедуры
//
//    &НаСервере
//    Функция НачатьВыполнениеНаСервере()
//     Возврат ДлительныеОперации.ВыполнитьПроцедуру(, "Обработка.МояОбработка.ВыполнитьРасчет", 
//      МойПараметр1, МойПараметр2);
//    КонецФункции
//    
//   3) Обработка результата длительной операции:
//    &НаКлиенте
//    Процедура ОбработатьРезультат(Результат, ДополнительныеПараметры) Экспорт
//     Если Результат = Неопределено Тогда
//      Возврат;
//     КонецЕсли;
//     ПриЗавершенииРасчета();
//    КонецПроцедуры 
//   
Функция ВыполнитьПроцедуру(Знач ПараметрыВыполнения = Неопределено, ИмяПроцедуры, Знач Параметр1 = Неопределено,
	Знач Параметр2 = Неопределено, Знач Параметр3 = Неопределено, Знач Параметр4 = Неопределено,
	Знач Параметр5 = Неопределено, Знач Параметр6 = Неопределено, Знач Параметр7 = Неопределено) Экспорт
	
	ПараметрыВызова = СписокПараметров(Параметр1, Параметр2, Параметр3, Параметр4,
		Параметр5, Параметр6, Параметр7);
		
	ПараметрыВыполнения = ПодготовитьПараметрыВыполнения(ПараметрыВыполнения, Ложь);
	
	Возврат ВыполнитьВФоне(ИмяПроцедуры, ПараметрыВызова, ПараметрыВыполнения);
	
КонецФункции

// АПК:141-вкл
// АПК:134-вкл
// АПК:142-вкл

// Запустить выполнение функции в многопоточном фоновом задании, если это возможно.
// Вызываемая процедура может быть с произвольным числом параметров, но не более 7.
// Значения передаваемых параметров процедуры, а также возвращаемое значение должны быть сериализуемыми.
// Параметры процедуры не должны быть возвращаемыми.
// Многопоточные длительные операции в неразделенном сеансе не поддерживаются.
//
// Не следует использовать эту функцию, если необходимо безусловно запускать фоновое задание.
// Рекомендуется применять совместно с функцией ДлительныеОперацииКлиент.ОжидатьЗавершение,
// если невозможно, тогда для проверки завершения операции использовать функцию ЗаданиеВыполнено.
//
// Параметры:
//  ИмяФункции - Строка - имя экспортной функции общего модуля, модуля менеджера объекта 
//                        или модуля обработки, которую необходимо выполнить в фоне.
//                        Например, "МойОбщийМодуль.МояПроцедура", "Отчеты.ЗагруженныеДанные.Сформировать"
//                        или "Обработки.ЗагрузкаДанных.МодульОбъекта.Загрузить". 
//  ПараметрыВыполнения - см. ПараметрыВыполненияФункции
//  НаборПараметровФункции - Соответствие из КлючИЗначение - произвольный набор параметров вызова функции:
//    * Ключ - Произвольный - ключ набора
//    * Значение - Массив - параметры вызова функции. Количество параметров может быть от 0 до 7.
//
// Возвращаемое значение:
//  Структура: 
//   * Статус               - Строка - "Выполняется", если задание еще не завершилось;
//                                     "Выполнено", если задание было успешно выполнено;
//                                     "Ошибка", если задание завершено с ошибкой;
//                                     "Отменено", если задание отменено пользователем или администратором.
//   * ИдентификаторЗадания - УникальныйИдентификатор - если Статус = "Выполняется", то содержит 
//                                     идентификатор запущенного фонового задания.
//                          - Неопределено - если Статус <> "Выполняется" и фоновое задание не запускалось.
//   * АдресРезультата       - Строка - адрес временного хранилища, в которое будет помещено Соответствие:
//                                      ** Ключ - Произвольный
//                                      ** Значение - см. ВыполнитьФункцию
//   * КраткоеПредставлениеОшибки   - Строка - краткая информация об исключении, если Статус = "Ошибка".
//   * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
//   * Сообщения - ФиксированныйМассив - если Статус <> "Выполняется", то массив объектов СообщениеПользователю,
//                                      которые были сформированы в фоновом задании.
//
Функция ВыполнитьФункциюВНесколькоПотоков(ИмяФункции, Знач ПараметрыВыполнения, Знач НаборПараметровФункции = Неопределено) Экспорт
	
	ПроверитьВозможностьЗапускаМногопоточнойДлительнойОперации(ПараметрыВыполнения, НаборПараметровФункции);

	Если ПараметрыВыполнения.ОжидатьЗавершение = ОбщиеПараметрыВыполненияВФоне().ОжидатьЗавершение Тогда
		ПараметрыВыполнения.ОжидатьЗавершение = 0;
	КонецЕсли;
	
	АдресРезультатов = Новый Соответствие;
	
	Если ТипЗнч(НаборПараметровФункции) = Тип("Соответствие") Тогда
		Для Каждого ПараметрФункции Из НаборПараметровФункции Цикл
			АдресРезультатаПотока = ПоместитьВоВременноеХранилище(Неопределено, Новый УникальныйИдентификатор);
			АдресРезультатов.Вставить(ПараметрФункции.Ключ, АдресРезультатаПотока);
		КонецЦикла;
		ПараметрыМетода = НаборПараметровФункции.Количество();
	Иначе
		ПараметрыМетода = НаборПараметровФункции; // Структура
		НаборПараметровФункции = Новый Соответствие;
	КонецЕсли;
	
	ИдентификаторПроцесса = Новый УникальныйИдентификатор;
	ПараметрыМногопоточнойОперации = ПараметрыМногопоточнойОперации(ИдентификаторПроцесса);
	ПараметрыМногопоточнойОперации.ИмяМетода = ИмяФункции;
	ПараметрыМногопоточнойОперации.ДляФункции = Истина;
	ПараметрыМногопоточнойОперации.ПараметрыВыполнения = ПараметрыВыполнения;
	ПараметрыМногопоточнойОперации.ПараметрыМетода = ПараметрыМетода;
	ПараметрыМногопоточнойОперации.АдресРезультатов = АдресРезультатов;
	
	ПодготовитьМногопоточнуюОперациюКЗапуску(ИмяФункции,
		АдресРезультатов, ИдентификаторПроцесса, НаборПараметровФункции);
	
	РезультатЗапуска = Новый Структура("Статус, ИдентификаторЗадания, АдресРезультата",
		СтатусДлительнойОперации().Выполняется);
	ЗапланироватьЗапускПотоковДлительныхОпераций(РезультатЗапуска, ПараметрыМногопоточнойОперации);
	
	РезультатЗапуска = ВыполнитьФункцию(ПараметрыВыполнения,
		ИмяМетодаМногопоточногоПроцесса(), ПараметрыМногопоточнойОперации);
	
	Если РезультатЗапуска.Статус <> СтатусДлительнойОперации().Выполняется Тогда
		УдалитьДанныеОПотоках(ИдентификаторПроцесса);
	КонецЕсли;
	
	Возврат РезультатЗапуска;
	
КонецФункции


// Запускает выполнение процедуры в многопоточном фоновом задании, если это возможно.
// Вызываемая процедура может быть с произвольным числом параметров, но не более 7.
// Значения передаваемых параметров процедуры, а также возвращаемое значение должны быть сериализуемыми.
// Параметры процедуры не должны быть возвращаемыми.
// Многопоточные длительные операции в неразделенном сеансе не поддерживаются.
//
// Не следует использовать эту функцию, если необходимо безусловно запускать фоновое задание.
// Рекомендуется применять совместно с функцией ДлительныеОперацииКлиент.ОжидатьЗавершение,
// если невозможно, тогда для проверки завершения операции использовать функцию ЗаданиеВыполнено.
//
// Параметры:
//  ИмяПроцедуры - Строка - имя экспортной процедуры общего модуля, модуля менеджера объекта 
//                          или модуля обработки, которую необходимо выполнить в фоне.
//  ПараметрыВыполнения - см. ПараметрыВыполненияПроцедуры
//  НаборПараметровПроцедуры - Соответствие из КлючИЗначение - произвольный набор параметров вызова процедуры:
//    * Ключ - Произвольный - ключ набора
//    * Значение - Массив - параметры вызова процедуры. Количество параметров может быть от 0 до 7.
//
// Возвращаемое значение:
//  Структура: 
//   * Статус               - Строка - "Выполняется", если задание еще не завершилось;
//                                     "Выполнено", если задание было успешно выполнено;
//                                     "Ошибка", если задание завершено с ошибкой;
//                                     "Отменено", если задание отменено пользователем или администратором.
//   * ИдентификаторЗадания - УникальныйИдентификатор - если Статус = "Выполняется", то содержит 
//                                     идентификатор запущенного фонового задания.
//                          - Неопределено - если Статус <> "Выполняется" и фоновое задание не запускалось.
//   * АдресРезультата       - Строка - адрес временного хранилища, в которое будет помещено Соответствие:
//                                       ** Ключ - Произвольный
//                                       ** Значение - см. ВыполнитьПроцедуру
//   * КраткоеПредставлениеОшибки   - Строка - краткая информация об исключении, если Статус = "Ошибка".
//   * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
//   * Сообщения - ФиксированныйМассив - если Статус <> "Выполняется", то массив объектов СообщениеПользователю,
//                                      которые были сформированы в фоновом задании.
//
Функция ВыполнитьПроцедуруВНесколькоПотоков(ИмяПроцедуры, Знач ПараметрыВыполнения, Знач НаборПараметровПроцедуры = Неопределено) Экспорт
	
	ПроверитьВозможностьЗапускаМногопоточнойДлительнойОперации(ПараметрыВыполнения, НаборПараметровПроцедуры);
	
	НовыеПараметрыВыполнения = ПараметрыВыполненияФункции(Неопределено);
	ЗаполнитьЗначенияСвойств(НовыеПараметрыВыполнения, ПараметрыВыполнения);
	ПараметрыВыполнения = НовыеПараметрыВыполнения;
	
	Если ПараметрыВыполнения.ОжидатьЗавершение = ОбщиеПараметрыВыполненияВФоне().ОжидатьЗавершение Тогда
		ПараметрыВыполнения.ОжидатьЗавершение = 0;
	КонецЕсли;
	
	АдресРезультатов = Новый Соответствие;
	
	Если ТипЗнч(НаборПараметровПроцедуры) = Тип("Соответствие") Тогда
		Для Каждого ПараметрФункции Из НаборПараметровПроцедуры Цикл
			АдресРезультатаПотока = ПоместитьВоВременноеХранилище(Неопределено, Новый УникальныйИдентификатор);
			АдресРезультатов.Вставить(ПараметрФункции.Ключ, АдресРезультатаПотока);
		КонецЦикла;
		ПараметрыМетода = НаборПараметровПроцедуры.Количество();
	Иначе
		ПараметрыМетода = НаборПараметровПроцедуры; // Структура
		НаборПараметровПроцедуры = Новый Соответствие;
	КонецЕсли;
	
	ИдентификаторПроцесса = Новый УникальныйИдентификатор;
	ПараметрыМногопоточнойОперации = ПараметрыМногопоточнойОперации(ИдентификаторПроцесса);
	ПараметрыМногопоточнойОперации.ИмяМетода = ИмяПроцедуры;
	ПараметрыМногопоточнойОперации.ДляФункции = Ложь;
	ПараметрыМногопоточнойОперации.ПараметрыВыполнения = ПараметрыВыполнения;
	ПараметрыМногопоточнойОперации.ПараметрыМетода = ПараметрыМетода;
	ПараметрыМногопоточнойОперации.АдресРезультатов = АдресРезультатов;
	
	ПодготовитьМногопоточнуюОперациюКЗапуску(ИмяПроцедуры,
		АдресРезультатов, ИдентификаторПроцесса, НаборПараметровПроцедуры);
	
	РезультатЗапуска = Новый Структура("Статус, ИдентификаторЗадания, АдресРезультата",
		СтатусДлительнойОперации().Выполняется);
	ЗапланироватьЗапускПотоковДлительныхОпераций(РезультатЗапуска, ПараметрыМногопоточнойОперации);
	
	РезультатЗапуска = ВыполнитьФункцию(ПараметрыВыполнения,
		ИмяМетодаМногопоточногоПроцесса(), ПараметрыМногопоточнойОперации);
	
	Если РезультатЗапуска.Статус <> СтатусДлительнойОперации().Выполняется Тогда
		УдалитьДанныеОПотоках(ИдентификаторПроцесса);
	КонецЕсли;
	
	Возврат РезультатЗапуска;
	
КонецФункции

// Конструктор коллекции ПараметрыВыполненияФункции для функции ВыполнитьФункцию.
//
// Если ЗапуститьВФоне = Ложь и ЗапуститьНеВФоне = Ложь, то задание будет выполнено в фоне по возможности.
// Запуск выполняется сразу в основном потоке при выполнении любого из следующих условий:
//  * если вызов выполняется в файловой базе во внешнем соединении (в этом режиме фоновые задания не поддерживаются);
//  * если приложение запущено в режиме отладки (параметр /C РежимОтладки) - для упрощения отладки конфигурации;
//  * если в файловой ИБ имеются активные фоновые задания - для снижения времени ожидания пользователя;
//  * если выполняется функция модуля внешней обработки или внешнего отчета.
//
// Параметры:
//   ИдентификаторФормы - УникальныйИдентификатор - уникальный идентификатор формы, 
//                               во временное хранилище которой надо поместить результат выполнения процедуры.
//
// Возвращаемое значение:
//   Структура - параметры выполнения длительной операции:
//     * ИдентификаторФормы  - УникальныйИдентификатор - уникальный идентификатор формы,
//                             во временное хранилище которой надо поместить результат выполнения процедуры.
//     * ОжидатьЗавершение   - Неопределено - ждать до момента завершения фонового задания.
//                           - Число - таймаут в секундах ожидания завершения фонового задания.
//                               Если задано 0, то ждать завершения задания не требуется.
//                               По умолчанию - 0.8 секунды, а для низкой скорости соединения - 4.
//     * НаименованиеФоновогоЗадания - Строка - описание фонового задания. По умолчанию - имя процедуры.
//     * КлючФоновогоЗадания - Строка - уникальный ключ для активных фоновых заданий, имеющих такое же имя процедуры.
//                                      По умолчанию не задан.
//     * АдресРезультата     - Строка - адрес временного хранилища, в которое должен быть помещен результат
//                                      работы процедуры. Если не задан, адрес формируется автоматически.
//     * ЗапуститьВФоне           - Булево - если Истина, то задание будет всегда выполняться в фоне, кроме ситуаций:
//                                  а) если вызов выполняется в файловой базе во внешнем соединении 
//                                  (в этом режиме фоновые задания не поддерживаются);
//                                  б) если выполняется функция модуля внешней обработки или внешнего отчета.
//                                  Кроме того, в файловом варианте при наличии ранее запущенных фоновых заданий,
//                                  новое задание становится в очередь и начинает выполняться после завершения предыдущих.
//                                  Если Ложь, то задание будет выполнено в фоне по возможности. 
//     * ЗапуститьНеВФоне         - Булево - если Истина, задание всегда будет запускаться непосредственно,
//                                  без использования фонового задания.
//     * БезРасширений            - Булево - если Истина, то фоновое задание будет запущено без подключения
//                                  расширений конфигурации. Имеет приоритет над параметром ЗапуститьНеВФоне. 
//     * СРасширениямиБазыДанных  - Булево - если Истина, то фоновое задание будет запущено с последней версией
//                                  расширений конфигурации. Имеет приоритет над параметром ЗапуститьНеВФоне.
//     * ВнешнийОтчетОбработка    - Неопределено - по умолчанию.
//                                - ДвоичныеДанные - когда указано имя метода, которое начинается
//                                    с "ВнешнийОтчет." или "ВнешняяОбработка.".
//                                    Для вызова такого метода требуется право интерактивного открытия
//                                    внешнего отчета или обработки соответственно.
//                                    Внешний отчет или обработка подключаются с защитой от опасных действий.
//     * ПрерватьВыполнениеЕслиОшибка - Булево - если Истина, то при возникновении ошибки в одном из дочерних фоновых
//                                  заданий, выполнение многопоточного фонового задания будет перервано.
//                                  Выполнение уже запущенных дочерних фоновых задания будет отменно.
//                                  Параметр только для функции ВыполнитьФункциюВНесколькоПотоков.
//
Функция ПараметрыВыполненияФункции(Знач ИдентификаторФормы) Экспорт
	
	Результат = ОбщиеПараметрыВыполненияВФоне();
	ДобавитьПараметрыВыполненияДляВозвратаРезультата(Результат, ИдентификаторФормы);
	
	Возврат Результат;
	
КонецФункции

// Конструктор коллекции ПараметрыВыполненияПроцедуры для функции ВыполнитьПроцедуру.
//
// Если ЗапуститьВФоне = Ложь и ЗапуститьНеВФоне = Ложь, то задание будет выполнено в фоне по возможности.
// Запуск выполняется сразу в основном потоке при выполнении любого из следующих условий:
//  * если вызов выполняется в файловой базе во внешнем соединении (в этом режиме фоновые задания не поддерживаются);
//  * если приложение запущено в режиме отладки (параметр /C РежимОтладки) - для упрощения отладки конфигурации;
//  * если в файловой ИБ имеются активные фоновые задания - для снижения времени ожидания пользователя;
//  * если выполняется функция модуля внешней обработки или внешнего отчета.
//
// Возвращаемое значение:
//   Структура - параметры выполнения длительной операции:
//     * ОжидатьЗавершение   - Неопределено - ждать до момента завершения фонового задания.
//                           - Число - таймаут в секундах ожидания завершения фонового задания.
//                               Если задано 0, то ждать завершения задания не требуется.
//                               По умолчанию - 0.8 секунды, а для низкой скорости соединения - 4.
//     * НаименованиеФоновогоЗадания - Строка - описание фонового задания. По умолчанию - имя процедуры.
//     * КлючФоновогоЗадания - Строка - уникальный ключ для активных фоновых заданий, имеющих такое же имя процедуры.
//                                      По умолчанию не задан.
//     * ЗапуститьВФоне           - Булево - если Истина, то задание будет всегда выполняться в фоне, кроме ситуаций:
//                                  а) если вызов выполняется в файловой базе во внешнем соединении 
//                                  (в этом режиме фоновые задания не поддерживаются);
//                                  б) если выполняется функция модуля внешней обработки или внешнего отчета.
//                                  Кроме того, в файловом варианте при наличии ранее запущенных фоновых заданий,
//                                  новое задание становится в очередь и начинает выполняться после завершения предыдущих.
//                                  Если Ложь, то задание будет выполнено в фоне по возможности. 
//     * ЗапуститьНеВФоне         - Булево - если Истина, задание всегда будет запускаться непосредственно,
//                                  без использования фонового задания.
//     * БезРасширений            - Булево - если Истина, то фоновое задание будет запущено без подключения
//                                  расширений конфигурации. Имеет приоритет над параметром ЗапуститьНеВФоне. 
//     * СРасширениямиБазыДанных  - Булево - если Истина, то фоновое задание будет запущено с последней версией
//                                  расширений конфигурации. Имеет приоритет над параметром ЗапуститьНеВФоне. 
//     * ВнешнийОтчетОбработка    - Неопределено - по умолчанию.
//                                - ДвоичныеДанные - когда указано имя метода, которое начинается
//                                    с "ВнешнийОтчет." или "ВнешняяОбработка.".
//                                    Для вызова такого метода требуется право интерактивного открытия
//                                    внешнего отчета или обработки соответственно.
//                                    Внешний отчет или обработка подключаются с защитой от опасных действий.
//     * ПрерватьВыполнениеЕслиОшибка - Булево - если Истина, то при возникновении ошибки в одном из дочерних фоновых
//                                  заданий, выполнение многопоточного фонового задания будет перервано.
//                                  Выполнение уже запущенных дочерних фоновых задания будет отменно.
//                                  Параметр только для функции ВыполнитьПроцедуруВНесколькоПотоков.
//
Функция ПараметрыВыполненияПроцедуры() Экспорт
	
	Возврат ОбщиеПараметрыВыполненияВФоне();
	
КонецФункции

// Вместо этой функции рекомендуется использовать функции ВыполнитьФункцию и ВыполнитьПроцедуру.
// 
// Запустить выполнение процедуры в фоновом задании, если это возможно.
// Не следует использовать эту функцию, если необходимо безусловно запускать фоновое задание.
// Рекомендуется применять совместно с функцией ДлительныеОперацииКлиент.ОжидатьЗавершение,
// если невозможно, тогда для проверки завершения операции использовать функцию ЗаданиеВыполнено.
// 
// Параметры:
//  ИмяПроцедуры           - Строка    - имя экспортной процедуры общего модуля, модуля менеджера объекта 
//                                       или модуля обработки, которую необходимо выполнить в фоне.
//                                       Например, "МойОбщийМодуль.МояПроцедура", "Отчет.ЗагруженныеДанные.Сформировать"
//                                       или "Обработка.ЗагрузкаДанных.МодульОбъекта.Загрузить". 
//                                       У процедуры должно быть два или три формальных параметра:
//                                        * Параметры       - Структура - произвольные параметры ПараметрыПроцедуры;
//                                        * АдресРезультата - Строка    - адрес временного хранилища, в которое нужно
//                                          поместить результат работы процедуры. Обязательно;
//                                        * АдресДополнительногоРезультата - Строка - если в ПараметрыВыполнения установлен 
//                                          параметр ДополнительныйРезультат, то содержит адрес дополнительного временного
//                                          хранилища, в которое нужно поместить результат работы процедуры. Опционально.
//                                       При необходимости выполнить в фоне функцию ее следует обернуть в процедуру,
//                                       а ее результат возвращать через второй параметр АдресРезультата.
//  ПараметрыПроцедуры     - Структура - произвольные параметры вызова процедуры ИмяПроцедуры.
//  ПараметрыВыполнения    - см. ДлительныеОперации.ПараметрыВыполненияВФоне
//
// Возвращаемое значение:
//  Структура: 
//   * Статус               - Строка - "Выполняется", если задание еще не завершилось;
//                                     "Выполнено", если задание было успешно выполнено;
//                                     "Ошибка", если задание завершено с ошибкой;
//                                     "Отменено", если задание отменено пользователем или администратором.
//   * ИдентификаторЗадания  - УникальныйИдентификатор - если Статус = "Выполняется", то содержит 
//                                     идентификатор запущенного фонового задания.
//                           - Неопределено - если Статус <> "Выполняется" и фоновое задание не запускалось.
//   * АдресРезультата       - Строка - адрес временного хранилища, в которое будет помещен результат работы процедуры
//                                      (или уже помещен, если Статус = "Выполнено").
//   * АдресДополнительногоРезультата - Строка - если установлен параметр ДополнительныйРезультат, 
//                                      содержит адрес дополнительного временного хранилища,
//                                      в которое будет помещен результат работы процедуры
//                                      (или уже помещен, если Статус = "Выполнено").
//   * КраткоеПредставлениеОшибки   - Строка - краткая информация об исключении, если Статус = "Ошибка".
//   * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
//   * Сообщения - ФиксированныйМассив - если Статус <> "Выполняется", то массив объектов СообщениеПользователю,
//                                      которые были сформированы в фоновом задании, иначе пустой массив.
// 
// Пример:
//  В общем виде процесс запуска и обработки результата длительной операции выглядит следующим образом:
//
//   1) Процедура, которая будет исполняться в фоне, располагается в модуле менеджера объекта или в серверном общем модуле:
//    Процедура ВыполнитьДействие(Параметры, АдресРезультата) Экспорт
//     ...
//     ПоместитьВоВременноеХранилище(Результат, АдресРезультата);
//    КонецПроцедуры
//
//   2) Запуск операции на сервере и подключение обработчика ожидания:
//    &НаКлиенте
//    Процедура ВыполнитьДействие()
//     ДлительнаяОперация = НачатьВыполнениеНаСервере();
//     ПараметрыОжидания = ДлительныеОперацииКлиент.ПараметрыОжидания(ЭтотОбъект);
//     ...
//     ОповещениеОЗавершении = Новый ОписаниеОповещения("ВыполнитьДействиеЗавершение", ЭтотОбъект);
//     ДлительныеОперацииКлиент.ОжидатьЗавершение(ДлительнаяОперация, ОповещениеОЗавершении, ПараметрыОжидания);
//    КонецПроцедуры
//
//    &НаСервере
//    Функция НачатьВыполнениеНаСервере()
//     ПараметрыПроцедуры = Новый Структура;
//     ...
//     ПараметрыВыполнения = ДлительныеОперации.ПараметрыВыполненияВФоне(УникальныйИдентификатор);
//     ...
//     Возврат ДлительныеОперации.ВыполнитьВФоне("Обработка.МояОбработка.ВыполнитьДействие", 
//     ПараметрыПроцедуры, ПараметрыВыполнения);
//    КонецФункции
//    
//   3) Обработка результата выполнения операции:
//    &НаКлиенте
//    Процедура ВыполнитьДействиеЗавершение(Результат, ДополнительныеПараметры) Экспорт
//     Если Результат = Неопределено Тогда
//      Возврат;
//     КонецЕсли;
//     ВывестиРезультат(Результат);
//    КонецПроцедуры 
//  
Функция ВыполнитьВФоне(Знач ИмяПроцедуры, Знач ПараметрыПроцедуры, Знач ПараметрыВыполнения) Экспорт
	
	// Обратная совместимость
	Если ПараметрыВыполнения.Свойство("ОжидатьЗавершения") И ПараметрыВыполнения.ОжидатьЗавершения <> -1 Тогда
		ПараметрыВыполнения.ОжидатьЗавершение = ПараметрыВыполнения.ОжидатьЗавершения;
	КонецЕсли;
	
	ОбщегоНазначенияКлиентСервер.ПроверитьПараметр("ДлительныеОперации.ВыполнитьВФоне", "ПараметрыВыполнения", 
		ПараметрыВыполнения, Тип("Структура")); 
	Если ПараметрыВыполнения.ЗапуститьНеВФоне И ПараметрыВыполнения.ЗапуститьВФоне Тогда
		ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр(
			"ru = 'Параметры ""%1"" и ""%2""
			|не могут одновременно принимать значение %3 в %4.'"),
			"ЗапуститьНеВФоне", "ЗапуститьВФоне", "Истина", "ДлительныеОперации.ВыполнитьВФоне");
	КонецЕсли;
	Если ПараметрыВыполнения.БезРасширений И ПараметрыВыполнения.СРасширениямиБазыДанных Тогда
		ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр(
			"ru = 'Параметры ""%1"" и ""%2""
			|не могут одновременно принимать значение %3 в %4.'"),
			"БезРасширений", "СРасширениямиБазыДанных", "Истина", "ДлительныеОперации.ВыполнитьВФоне");
	КонецЕсли;
	
#Если ВнешнееСоединение Тогда
	ИнформационнаяБазаФайловая = ОбщегоНазначения.ИнформационнаяБазаФайловая();
	Если ПараметрыВыполнения.БезРасширений И ИнформационнаяБазаФайловая Тогда
		ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр(
			"ru = 'Фоновое задание не может быть запущено с параметром ""%1""
			|во внешнем соединении с файловой информационной базой в %2.'"),
			"БезРасширений", "ДлительныеОперации.ВыполнитьВФоне");
	ИначеЕсли ПараметрыВыполнения.СРасширениямиБазыДанных И ИнформационнаяБазаФайловая Тогда
		ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр(
			"ru = 'Фоновое задание не может быть запущено с параметром ""%1""
			|во внешнем соединении с файловой информационной базой в %2.'"),
			"СРасширениямиБазыДанных", "ДлительныеОперации.ВыполнитьВФоне");
	КонецЕсли;
#КонецЕсли
		
	Результат = Новый Структура;
	Результат.Вставить("Статус", "Выполняется");
	Результат.Вставить("ИдентификаторЗадания", Неопределено);
	Если ПараметрыВыполнения.Свойство("АдресРезультата")
	   И Не ПараметрыВыполнения.Свойство("СвойстваИсполняющегоПотокаМногопоточнойДлительнойОперации") Тогда
		Если ПараметрыВыполнения.АдресРезультата = Неопределено Тогда
			Если Не ЗначениеЗаполнено(ПараметрыВыполнения.ИдентификаторФормы) И ОбщегоНазначения.РежимОтладки() Тогда
				Попытка
					ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр(
						"ru = 'Не указан ни уникальный идентификатор формы в параметре %1,
						|ни адрес временного хранилища в %2 в %3.
						|Убедиться, что при обработке результата временное хранилище очищается явно методом %4.'"),
						"ПараметрыВыполнения.ИдентификаторФормы", "ПараметрыВыполнения.АдресРезультата",
						"ДлительныеОперации.ВыполнитьВФоне", "УдалитьИзВременногоХранилища");
				Исключение
					// АПК:154-выкл Не ошибка, а предупреждение (рекомендация разработчику).
					ЗаписьЖурналаРегистрации(НСтр("ru = 'Длительные операции.Диагностика'", ОбщегоНазначения.КодОсновногоЯзыка()),
						УровеньЖурналаРегистрации.Предупреждение, , , ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
					// АПК:154-вкл 
				КонецПопытки;
			КонецЕсли;
			ПараметрыВыполнения.АдресРезультата = ПоместитьВоВременноеХранилище(Неопределено, ПараметрыВыполнения.ИдентификаторФормы);
		ИначеЕсли Не ЭтоАдресВременногоХранилища(ПараметрыВыполнения.АдресРезультата) Тогда
			ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(НСтр(
				"ru = 'Не указан адрес временного хранилища в параметре %1
				|в %2.'"),
				"ПараметрыВыполнения.АдресРезультата", "ДлительныеОперации.ВыполнитьВФоне");
		КонецЕсли;	
		Результат.Вставить("АдресРезультата", ПараметрыВыполнения.АдресРезультата);
	КонецЕсли;
	Если ПараметрыВыполнения.Свойство("ДополнительныйРезультат") Тогда
		Результат.Вставить("АдресДополнительногоРезультата", "");
	КонецЕсли;
	Результат.Вставить("КраткоеПредставлениеОшибки", "");
	Результат.Вставить("ПодробноеПредставлениеОшибки", "");
	Результат.Вставить("Сообщения", Новый ФиксированныйМассив(Новый Массив));
	
	Если ПараметрыВыполнения.БезРасширений Тогда
		ПараметрыВыполнения.БезРасширений = ЗначениеЗаполнено(ПараметрыСеанса.ПодключенныеРасширения);
		
	ИначеЕсли Не ПараметрыВыполнения.СРасширениямиБазыДанных
	        И Не ПараметрыВыполнения.ЗапуститьНеВФоне
	        И СтандартныеПодсистемыСервер.ЭтоРазделенныйРежимСеансаБезРазделителей() Тогда
		
		ПараметрыВыполнения.СРасширениямиБазыДанных = Истина;
	КонецЕсли;
	
	ПараметрыЭкспортнойПроцедуры = ПараметрыПроцедуры;
	Если Не ПараметрыВыполнения.Свойство("ЭтоФункция") Тогда
		ПараметрыЭкспортнойПроцедуры = Новый Массив;
		ПараметрыЭкспортнойПроцедуры.Добавить(ПараметрыПроцедуры);
		ПараметрыЭкспортнойПроцедуры.Добавить(ПараметрыВыполнения.АдресРезультата);
	КонецЕсли;
	
	Если ПараметрыВыполнения.Свойство("ДополнительныйРезультат") И ПараметрыВыполнения.ДополнительныйРезультат Тогда
		Результат.АдресДополнительногоРезультата = ПоместитьВоВременноеХранилище(Неопределено, ПараметрыВыполнения.ИдентификаторФормы);
		ПараметрыЭкспортнойПроцедуры.Добавить(Результат.АдресДополнительногоРезультата);
	КонецЕсли;
	
#Если ВнешнееСоединение Тогда
	ВыполнитьБезФоновогоЗадания = ИнформационнаяБазаФайловая 
		Или ОбщегоНазначения.РежимОтладки() Или ПараметрыВыполнения.ЗапуститьНеВФоне
		Или (ЕстьФоновыеЗаданияВФайловойИБ() И Не ПараметрыВыполнения.ЗапуститьВФоне) 
		Или Не ВозможноВыполнитьВФоне(ИмяПроцедуры);
#Иначе
	ВыполнитьБезФоновогоЗадания = Не ПараметрыВыполнения.БезРасширений
		И Не ПараметрыВыполнения.СРасширениямиБазыДанных
		И (ОбщегоНазначения.РежимОтладки() Или ПараметрыВыполнения.ЗапуститьНеВФоне
			Или (ЕстьФоновыеЗаданияВФайловойИБ() И Не ПараметрыВыполнения.ЗапуститьВФоне) 
			Или Не ВозможноВыполнитьВФоне(ИмяПроцедуры));
#КонецЕсли

	// Выполнить в основном потоке.
	Если ВыполнитьБезФоновогоЗадания Тогда
		Попытка
			Если ПараметрыВыполнения.Свойство("ЭтоФункция") И ПараметрыВыполнения.ЭтоФункция Тогда
				ВызватьФункцию(ИмяПроцедуры, ПараметрыЭкспортнойПроцедуры, ПараметрыВыполнения);
			Иначе
				ВызватьПроцедуру(ИмяПроцедуры, ПараметрыЭкспортнойПроцедуры, ПараметрыВыполнения);
			КонецЕсли;
			Результат.Статус = "Выполнено";
		Исключение
			Результат.Статус = "Ошибка";
			УстановитьСвойстваОшибки(Результат, ИнформацияОбОшибке());
			ЗаписьЖурналаРегистрации(НСтр("ru = 'Длительные операции.Ошибка выполнения'", ОбщегоНазначения.КодОсновногоЯзыка()),
				УровеньЖурналаРегистрации.Ошибка, , , Результат.ПодробноеПредставлениеОшибки);
		КонецПопытки;
		Возврат Результат;
	КонецЕсли;
	
	// Выполнить в фоне.
	БезопасныйРежим = БезопасныйРежим();
	УстановитьОтключениеБезопасногоРежима(Истина);
	Попытка
		Задание = ЗапуститьФоновоеЗаданиеСКонтекстомКлиента(ИмяПроцедуры,
			ПараметрыВыполнения, ПараметрыЭкспортнойПроцедуры, БезопасныйРежим,
			ПараметрыВыполнения.ОжидатьЗавершение <> Неопределено);
	Исключение
		Результат.Статус = "Ошибка";
		Если Задание <> Неопределено И Задание.ИнформацияОбОшибке <> Неопределено Тогда
			УстановитьСвойстваОшибки(Результат, Задание.ИнформацияОбОшибке);
		Иначе
			УстановитьСвойстваОшибки(Результат, ИнформацияОбОшибке());
		КонецЕсли;
		Возврат Результат;
	КонецПопытки;
	УстановитьОтключениеБезопасногоРежима(Ложь);
	
	Если Задание <> Неопределено И Задание.ИнформацияОбОшибке <> Неопределено Тогда
		Результат.Статус = "Ошибка";
		УстановитьСвойстваОшибки(Результат, Задание.ИнформацияОбОшибке);
		Возврат Результат;
	КонецЕсли;
	
	Результат.ИдентификаторЗадания = Задание.УникальныйИдентификатор;
	Если ИмяПроцедуры = ИмяМетодаМногопоточногоПроцесса()
	   И Не ПараметрыВыполнения.Свойство("ЭтоПерезапускУправляющегоПотока") Тогда
		ЗапланироватьЗапускПотоковДлительныхОпераций(Результат, ПараметрыЭкспортнойПроцедуры[0]);
	КонецЕсли;
	ЗаданиеВыполнено = Ложь;
	
	Сообщения = Новый Массив;
	Если ПараметрыВыполнения.ОжидатьЗавершение <> 0 Тогда
		Пока Истина Цикл
			Задание = Задание.ОжидатьЗавершенияВыполнения(ПараметрыВыполнения.ОжидатьЗавершение);
			Если Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
				Прервать;
			КонецЕсли;
			Если ПараметрыВыполнения.ОжидатьЗавершение = Неопределено Тогда
				ОбщегоНазначенияКлиентСервер.ДополнитьМассив(Сообщения,
					Задание.ПолучитьСообщенияПользователю(Истина));
			КонецЕсли;
			ТекущийРезультат = ОперацияВыполнена(Результат.ИдентификаторЗадания, Задание);
			Если ТекущийРезультат.Статус <> "Выполняется" Тогда
				ЗаданиеВыполнено = Истина;
				Прервать;
			КонецЕсли;
		КонецЦикла;
	КонецЕсли;
	
	Если ЗаданиеВыполнено Тогда
		Если ПараметрыВыполнения.ОжидатьЗавершение <> Неопределено Тогда
			Сообщения = ПолучитьИзОповещений(Истина, Результат.ИдентификаторЗадания, "Сообщения");
		КонецЕсли;
		Результат.Сообщения = Сообщения;
	КонецЕсли;
	
	ЗаполнитьЗначенияСвойств(Результат, ОперацияВыполнена(Результат.ИдентификаторЗадания), , "Сообщения");
	Возврат Результат;
	
КонецФункции

// Возвращает новую структуру для параметра ПараметрыВыполнения функции ВыполнитьВФоне.
//
// Если ЗапуститьВФоне = Ложь и ЗапуститьНеВФоне = Ложь, то задание будет выполнено в фоне по возможности.
// Запуск выполняется сразу в основном потоке при выполнении любого из следующих условий:
//  * если вызов выполняется в файловой базе во внешнем соединении (в этом режиме фоновые задания не поддерживаются);
//  * если приложение запущено в режиме отладки (параметр /C РежимОтладки) - для упрощения отладки конфигурации;
//  * если в файловой ИБ имеются активные фоновые задания - для снижения времени ожидания пользователя;
//  * если выполняется функция модуля внешней обработки или внешнего отчета.
//
// Параметры:
//   ИдентификаторФормы - УникальныйИдентификатор - уникальный идентификатор формы, во временное хранилище которой 
//                                                  необходимо поместить результат выполнения процедуры.
//                      - Неопределено - если временное хранилище не нужно создавать автоматически на время жизни
//                                       формы, а его адрес предполагается задавать явно в свойстве АдресРезультата.
//                                       В таком случае, временное хранилище нужно очищать явно при обработке результата
//                                       длительной операции с помощью метода УдалитьИзВременногоХранилища.
// Возвращаемое значение:
//   Структура:
//     * ИдентификаторФормы      - УникальныйИдентификатор - уникальный идентификатор формы, 
//                                 во временное хранилище которой надо поместить результат выполнения процедуры.
//     * ДополнительныйРезультат - Булево     - признак использования дополнительного временного хранилища для передачи 
//                                 результата из фонового задания в родительский сеанс. По умолчанию - Ложь.
//     * ОжидатьЗавершение       - Неопределено - ждать до момента завершения фонового задания.
//                               - Число - таймаут в секундах ожидания завершения фонового задания.
//                                   Если задано 0, то ждать завершения задания не требуется.
//                                   По умолчанию - 0.8 секунды, а для низкой скорости соединения - 4.
//     * НаименованиеФоновогоЗадания - Строка - описание фонового задания. По умолчанию - имя процедуры.
//     * КлючФоновогоЗадания      - Строка    - уникальный ключ для активных фоновых заданий, имеющих такое же имя процедуры.
//                                              По умолчанию не задан.
//     * АдресРезультата          - Строка - адрес временного хранилища, в которое должен быть помещен результат
//                                           работы процедуры. Если не задан, адрес формируется автоматически 
//                                           на время жизни формы с помощью идентификатора ИдентификаторФормы.
//     * ЗапуститьВФоне           - Булево - если Истина, то задание будет всегда выполняться в фоне, кроме ситуаций:
//                                  а) если вызов выполняется в файловой базе во внешнем соединении 
//                                  (в этом режиме фоновые задания не поддерживаются);
//                                  б) если выполняется функция модуля внешней обработки или внешнего отчета.
//                                  Кроме того, в файловом варианте при наличии ранее запущенных фоновых заданий,
//                                  новое задание становится в очередь и начинает выполняться после завершения предыдущих.
//                                  Если Ложь, то задание будет выполнено в фоне по возможности. 
//     * ЗапуститьНеВФоне         - Булево - если Истина, задание всегда будет запускаться непосредственно,
//                                  без использования фонового задания.
//     * БезРасширений            - Булево - если Истина, то фоновое задание будет запущено без подключения
//                                  расширений конфигурации. Имеет приоритет над параметром ЗапуститьНеВФоне. 
//     * СРасширениямиБазыДанных  - Булево - если Истина, то фоновое задание будет запущено с последней версией
//                                  расширений конфигурации. Имеет приоритет над параметром ЗапуститьНеВФоне. 
//     * ВнешнийОтчетОбработка    - Неопределено - по умолчанию.
//                                - ДвоичныеДанные - когда указано имя метода, которое начинается
//                                    с "ВнешнийОтчет." или "ВнешняяОбработка.".
//                                    Для вызова такого метода требуется право интерактивного открытия
//                                    внешнего отчета или обработки соответственно.
//                                    Внешний отчет или обработка подключаются с защитой от опасных действий.
//
Функция ПараметрыВыполненияВФоне(Знач ИдентификаторФормы = Неопределено) Экспорт
	
	Результат = ОбщиеПараметрыВыполненияВФоне();
	ДобавитьПараметрыВыполненияДляВозвратаРезультата(Результат, ИдентификаторФормы);
	Результат.Вставить("ДополнительныйРезультат", Ложь);
	
	Возврат Результат;
	
КонецФункции

// Регистрирует информацию о ходе выполнения длительной операции.
// Не следует использовать для передачи результата работы длительной операции по частям.
//
// Во избежание избыточного потребления памяти и ее утечек при выполнении одной длительной операции
// не следует сообщать прогресс более 100 раз.
//
// Если прогресс сообщается менее чем через 3 сек после предыдущего вызова, то последнее сообщение
// удаляется и заменяется на новое (если используется система взаимодействия, то отправка нового
// сообщения не выполняется, а будет выполнена спустя некоторое время - не быстрее 3 сек).
//
// После отправки прогресса отправляются все сообщения, накопленные в очереди сообщений фонового задания.
//
// Для того чтобы выводить ход выполнения длительной операции для пользователя, следует установить 
// свойство ВыводитьПрогрессВыполнения в Истина (см. ДлительныеОперацииКлиент.ПараметрыОжидания).
//
// Параметры:
//  Процент                 - Число        - процент выполнения.
//  Текст                   - Строка       - информация о текущей операции.
//  ДополнительныеПараметры - Произвольный - любая дополнительная информация, которую необходимо передать на клиент. 
//                                           Значение должно быть простым (сериализуемым в XML-строку).
//
Процедура СообщитьПрогресс(Знач Процент = Неопределено, Знач Текст = Неопределено, Знач ДополнительныеПараметры = Неопределено) Экспорт
	
	Если Не СтандартныеПодсистемыПовтИсп.ЭтоСеансДлительнойОперации() Тогда
		Возврат;
	КонецЕсли;
	
	ПередаваемоеЗначение = Новый Структура;
	Если Процент <> Неопределено Тогда
		ПередаваемоеЗначение.Вставить("Процент", Процент);
	КонецЕсли;
	Если Текст <> Неопределено Тогда
		ПередаваемоеЗначение.Вставить("Текст", Текст);
	КонецЕсли;
	Если ДополнительныеПараметры <> Неопределено Тогда
		ПередаваемоеЗначение.Вставить("ДополнительныеПараметры", ДополнительныеПараметры);
	КонецЕсли;
	
	ОтправитьОповещениеКлиенту("Прогресс", ПередаваемоеЗначение);
	
КонецПроцедуры

// Считывает информацию о ходе выполнения длительной операции, 
// записанную процедурой ДлительныеОперации.СообщитьПрогресс.
//
// Рекомендуется получать прогресс через обработчик оповещения,
// который подключается с помощью ДлительныеОперацииКлиент.ОжидатьЗавершение.
//
// Параметры:
//   ИдентификаторЗадания - УникальныйИдентификатор - идентификатор фонового задания.
//
// Возвращаемое значение:
//   Неопределено, Структура - информация о ходе выполнения фонового задания, записанная процедурой СообщитьПрогресс:
//    * Процент                 - Число  - необязательный. Процент выполнения.
//    * Текст                   - Строка - необязательный. Информация о текущей операции.
//    * ДополнительныеПараметры - Произвольный - необязательный. Любая дополнительная информация.
//
Функция ПрочитатьПрогресс(Знач ИдентификаторЗадания) Экспорт
	
	Возврат ПолучитьИзОповещений(Истина, ИдентификаторЗадания, "Прогресс");
	
КонецФункции

// Отменяет выполнение фонового задания по переданному идентификатору.
// При этом если в длительной операции открывались транзакции, то будет произведен откат последней открытой транзакции.
//
// Таким образом, если длительная операция выполняет обработку (запись) данных, то для полной отмены всей операции
// следует выполнять запись в одной транзакции (в таком случае, будет отменена вся транзакция целиком).
// Если же достаточно, чтобы длительная операция была не отменена целиком, а прервана на достигнутом этапе,
// то, напротив, открывать одну длинную транзакцию не требуется.
// 
// Параметры:
//  ИдентификаторЗадания - УникальныйИдентификатор - идентификатор фонового задания, полученный при запуске
//                           длительной операции. Смотри ДлительныеОперации.ВыполнитьВФоне.
// 
Процедура ОтменитьВыполнениеЗадания(Знач ИдентификаторЗадания) Экспорт 
	
	Если Не ЗначениеЗаполнено(ИдентификаторЗадания) Тогда
		Возврат;
	КонецЕсли;
	
	УстановитьПривилегированныйРежим(Истина);
	Если ПараметрыСеанса.ДлительныеОперации.Отмененные.Найти(ИдентификаторЗадания) = Неопределено Тогда
		Свойства = Новый Структура(ПараметрыСеанса.ДлительныеОперации);
		Отмененные = Новый Массив(Свойства.Отмененные);
		Отмененные.Добавить(ИдентификаторЗадания);
		Свойства.Отмененные = Новый ФиксированныйМассив(Отмененные);
		ПараметрыСеанса.ДлительныеОперации = Новый ФиксированнаяСтруктура(Свойства);
	КонецЕсли;
	УстановитьПривилегированныйРежим(Ложь);
	
	Задание = НайтиЗаданиеПоИдентификатору(ИдентификаторЗадания);
	Если Задание = Неопределено Или Задание.Состояние <> СостояниеФоновогоЗадания.Активно Тогда
		Возврат;
	КонецЕсли;
	
	Попытка
		Задание.Отменить();
	Исключение
		// Возможно задание как раз в этот момент закончилось и ошибки нет.
		ЗаписьЖурналаРегистрации(НСтр("ru = 'Длительные операции.Отмена выполнения фонового задания'", ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрации.Информация, , , ОбработкаОшибок.КраткоеПредставлениеОшибки(ИнформацияОбОшибке()));
	КонецПопытки;
	
КонецПроцедуры

// Проверяет состояние фонового задания по переданному идентификатору.
// При аварийном завершении задания вызывает исключение с текстом ошибки,
// возникшим в нем, и уточнением вида "См. также журнал регистрации".
//
// Параметры:
//  ИдентификаторЗадания - УникальныйИдентификатор - идентификатор фонового задания.
//  РасширенныйРезультат - Булево - если Истина, вернуть структуру.
//
// Возвращаемое значение:
//  Булево - состояние выполнения задания.
//  Структура:
//   * Статус      - Строка - "Выполняется", если задание еще не завершилось;
//                            "Выполнено", если задание было успешно выполнено;
//                            "Ошибка", если задание завершено с ошибкой;
//                            "Отменено", если задание отменено пользователем или администратором.
//
//   * ТекстОшибки - Строка - текст ошибки для вызова исключения (как при возврате краткого результата),
//                            если Статус = "Ошибка" или Статус = "Отменено".
//
//   * КраткоеПредставлениеОшибки   - Строка - краткая информация об исключении, если Статус = "Ошибка".
//   * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
//   * ИдентификаторЗадания         - УникальныйИдентификатор - последний идентификатор задания,
//                                      если были перезапуски (например, при многопоточной длительной операции)
//                                      или тот же идентификатор, что передан в функцию, когда перезапусков не было.
//
//   * Задание - Неопределено   - задание не найдено.
//             - ФоновоеЗадание - когда задание найдено.
//
Функция ЗаданиеВыполнено(Знач ИдентификаторЗадания, РасширенныйРезультат = Ложь) Экспорт
	
	ОбщегоНазначенияКлиентСервер.ПроверитьПараметр("ДлительныеОперации.ЗаданиеВыполнено",
		"ИдентификаторЗадания", ИдентификаторЗадания, Тип("УникальныйИдентификатор"));
	
	ОбщегоНазначенияКлиентСервер.ПроверитьПараметр("ДлительныеОперации.ЗаданиеВыполнено",
		"РасширенныйРезультат", РасширенныйРезультат, Тип("Булево"));
	
	Задание = Неопределено;
	Результат = ОперацияВыполнена(ИдентификаторЗадания, Задание);
	
	Если РасширенныйРезультат Тогда
		Свойства = Новый Структура;
		Свойства.Вставить("Статус",                       Результат.Статус);
		Свойства.Вставить("ТекстОшибки",                  "");
		Свойства.Вставить("КраткоеПредставлениеОшибки",   Результат.КраткоеПредставлениеОшибки);
		Свойства.Вставить("ПодробноеПредставлениеОшибки", Результат.ПодробноеПредставлениеОшибки);
		Свойства.Вставить("ИдентификаторЗадания",         ПоследнийИдентификатор(ИдентификаторЗадания));
		Свойства.Вставить("Задание",                      Задание);
	КонецЕсли;
	
	Если Результат.Статус = "Выполняется" Тогда
		Возврат ?(РасширенныйРезультат, Свойства, Ложь);
	ИначеЕсли Результат.Статус = "Выполнено" Тогда
		Возврат ?(РасширенныйРезультат, Свойства, Истина);
	КонецЕсли;
	
	Если Результат.Статус = "Отменено" Тогда
		ТекстОшибки = НСтр("ru = 'Операция отменена'");
		
	ИначеЕсли Задание = Неопределено Тогда
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = '%1
			           |
			           |Технические подробности:
			           |%2
			           |
			           |См. также журнал регистрации.'"),
			Результат.КраткоеПредставлениеОшибки,
			Результат.ПодробноеПредставлениеОшибки);
	Иначе
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = '%1
			           |
			           |Технические подробности:
			           |Ошибка выполнения фонового задания %2 с идентификатором %3 по причине
			           |%4
			           |
			           |См. также журнал регистрации.'"),
			Результат.КраткоеПредставлениеОшибки,
			Задание.ИмяМетода,
			Строка(ИдентификаторЗадания),
			Результат.ПодробноеПредставлениеОшибки);
	КонецЕсли;
	
	Если РасширенныйРезультат Тогда
		Свойства.ТекстОшибки = ТекстОшибки;
		Возврат Свойства;
	КонецЕсли;
	
	ВызватьИсключение ТекстОшибки;
	
КонецФункции

// Получает сообщения пользователю из фонового задания длительной операции.
//
// Рекомендуется получать сообщения через обработчик оповещения,
// который подключается с помощью ДлительныеОперацииКлиент.ОжидатьЗавершение.
// 
// Параметры:
//  УдалятьПолученные    - Булево                  - признак необходимости удаления полученных сообщений.
//  ИдентификаторЗадания - УникальныйИдентификатор - идентификатор фонового задания, соответствующего длительной 
//                                                   операции, у которой требуется получить сообщения пользователю. 
//                                                   Если не задан, то сообщения пользователю возвращаются
//                                                   из сеанса текущего пользователя.
// 
// Возвращаемое значение:
//  ФиксированныйМассив - объекты СообщениеПользователю, которые были сформированы в фоновом задании.
//
// Пример:
//   Операция = ДлительныеОперации.ВыполнитьВФоне(...);
//   ...
//   Сообщения = ДлительныеОперации.СообщенияПользователю(Истина, Операция.ИдентификаторЗадания);
//
Функция СообщенияПользователю(УдалятьПолученные = Ложь, ИдентификаторЗадания = Неопределено) Экспорт
	
	Если ЗначениеЗаполнено(ИдентификаторЗадания) Тогда
		Возврат ПолучитьИзОповещений(УдалятьПолученные, ИдентификаторЗадания, "Сообщения");
	КонецЕсли;
	
	Возврат ПолучитьСообщенияПользователю(УдалятьПолученные);
	
КонецФункции

#Область УстаревшиеПроцедурыИФункции

// Устарела. Следует использовать ВыполнитьВФоне.
//
// Запускает выполнение процедуры в фоновом задании.
// Является менее функциональным аналогом ВыполнитьВФоне, предусмотрена для обратной совместимости.
// 
// Параметры:
//  ИдентификаторФормы     - УникальныйИдентификатор - идентификатор формы, 
//                           из которой выполняется запуск длительной операции. 
//  ИмяЭкспортнойПроцедуры - Строка - имя экспортной процедуры, 
//                           которую необходимо выполнить в фоне.
//  Параметры              - Структура - все необходимые параметры для 
//                           выполнения процедуры ИмяЭкспортнойПроцедуры.
//  НаименованиеЗадания    - Строка - наименование фонового задания. 
//                           Если не задано, то будет равно ИмяЭкспортнойПроцедуры. 
//  ИспользоватьДополнительноеВременноеХранилище - Булево - признак использования
//                           дополнительного временного хранилища для передачи данных
//                           в родительский сеанс из фонового задания. По умолчанию - Ложь.
//
// Возвращаемое значение:
//  Структура              - параметры выполнения задания: 
//   * АдресХранилища  - Строка     - адрес временного хранилища, в которое будет
//                                    помещен результат работы задания;
//   * АдресХранилищаДополнительный - Строка - адрес дополнительного временного хранилища,
//                                    в которое будет помещен результат работы задания (доступно только если 
//                                    установлен параметр ИспользоватьДополнительноеВременноеХранилище);
//   * ИдентификаторЗадания - УникальныйИдентификатор - уникальный идентификатор запущенного фонового задания;
//                          - Неопределено - если фоновое задание не запускалось;
//   * ЗаданиеВыполнено - Булево - Истина если задание было успешно выполнено за время вызова функции.
// 
Функция ЗапуститьВыполнениеВФоне(Знач ИдентификаторФормы, Знач ИмяЭкспортнойПроцедуры, Знач Параметры,
	Знач НаименованиеЗадания = "", ИспользоватьДополнительноеВременноеХранилище = Ложь) Экспорт
	
	АдресХранилища = ПоместитьВоВременноеХранилище(Неопределено, ИдентификаторФормы);
	
	Результат = Новый Структура;
	Результат.Вставить("АдресХранилища",       АдресХранилища);
	Результат.Вставить("ЗаданиеВыполнено",     Ложь);
	Результат.Вставить("ИдентификаторЗадания", Неопределено);
	
	Если Не ЗначениеЗаполнено(НаименованиеЗадания) Тогда
		НаименованиеЗадания = ИмяЭкспортнойПроцедуры;
	КонецЕсли;
	
	ПараметрыЭкспортнойПроцедуры = Новый Массив;
	ПараметрыЭкспортнойПроцедуры.Добавить(Параметры);
	ПараметрыЭкспортнойПроцедуры.Добавить(АдресХранилища);
	
	Если ИспользоватьДополнительноеВременноеХранилище Тогда
		АдресХранилищаДополнительный = ПоместитьВоВременноеХранилище(Неопределено, ИдентификаторФормы);
		ПараметрыЭкспортнойПроцедуры.Добавить(АдресХранилищаДополнительный);
	КонецЕсли;
	
	ЗапущеноЗаданий = 0;
	Если ОбщегоНазначения.ИнформационнаяБазаФайловая()
		И Не ОбновлениеИнформационнойБазы.НеобходимоОбновлениеИнформационнойБазы() Тогда
		Отбор = Новый Структура;
		Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);
		ЗапущеноЗаданий = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор).Количество();
	КонецЕсли;
	
	Если ОбщегоНазначения.РежимОтладки()
		Или ЗапущеноЗаданий > 0 Тогда
		ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяЭкспортнойПроцедуры, ПараметрыЭкспортнойПроцедуры);
		Результат.ЗаданиеВыполнено = Истина;
	Иначе
		ВремяОжидания = ?(ПолучитьСкоростьКлиентскогоСоединения() = СкоростьКлиентскогоСоединения.Низкая, 4, 2);
		ПараметрыВыполнения = ПараметрыВыполненияВФоне(Неопределено);
		ПараметрыВыполнения.НаименованиеФоновогоЗадания = НаименованиеЗадания;
		БезопасныйРежим = БезопасныйРежим();
		УстановитьОтключениеБезопасногоРежима(Истина);
		Задание = ЗапуститьФоновоеЗаданиеСКонтекстомКлиента(ИмяЭкспортнойПроцедуры,
			ПараметрыВыполнения, ПараметрыЭкспортнойПроцедуры, БезопасныйРежим);
		УстановитьОтключениеБезопасногоРежима(Ложь);
		
		Задание = Задание.ОжидатьЗавершенияВыполнения(ВремяОжидания);
		
		Статус = ОперацияВыполнена(Задание.УникальныйИдентификатор);
		Результат.ЗаданиеВыполнено = Статус.Статус = "Выполнено";
		Результат.ИдентификаторЗадания = Задание.УникальныйИдентификатор;
	КонецЕсли;
	
	Если ИспользоватьДополнительноеВременноеХранилище Тогда
		Результат.Вставить("АдресХранилищаДополнительный", АдресХранилищаДополнительный);
	КонецЕсли;
	
	Возврат Результат;
	
КонецФункции

#КонецОбласти

#КонецОбласти

#Область СлужебныйПрограммныйИнтерфейс

// Параметры:
//  ИдентификаторЗадания - УникальныйИдентификатор
//  Задание - ФоновоеЗадание - возвращаемое значение - найденное фоновое задание.
//          - Неопределено - если фоновое задание не найдено.
//
// Возвращаемое значение:
//   см. НовыйРезультатВыполненияОперации
//
Функция ОперацияВыполнена(Знач ИдентификаторЗадания, Задание = Неопределено) Экспорт
	
	ОбщегоНазначенияКлиентСервер.ПроверитьПараметр("ДлительныеОперации.ОперацияВыполнена",
		"ИдентификаторЗадания", ИдентификаторЗадания, Тип("УникальныйИдентификатор"));
	
	Результат = НовыйРезультатВыполненияОперации();
	ПоследнийИдентификатор = ПоследнийИдентификатор(ИдентификаторЗадания);
	
	Задание = НайтиЗаданиеПоИдентификатору(ПоследнийИдентификатор);
	Если Задание = Неопределено Тогда
		РезультатИзОповещения = ПолучитьИзОповещений(Ложь,
			ИдентификаторЗадания, "ДлительнаяОперацияЗавершена");
		Если РезультатИзОповещения <> Неопределено Тогда
			ЗаполнитьЗначенияСвойств(Результат, РезультатИзОповещения);
			Возврат Результат;
		КонецЕсли;
		Если ВыполненПерезапускУправляющегоПотока(ИдентификаторЗадания, Задание) Тогда
			Возврат Результат;
		КонецЕсли;
		Результат.КраткоеПредставлениеОшибки =
			НСтр("ru = 'Операция не выполнена из-за аварийного завершения фонового задания.'");
		Результат.ПодробноеПредставлениеОшибки = Результат.КраткоеПредставлениеОшибки + Символы.ПС
			+ НСтр("ru = 'Фоновое задание не существует'") + ": " + Строка(ПоследнийИдентификатор);
		ЗаписьЖурналаРегистрации(НСтр("ru = 'Длительные операции.Фоновое задание не найдено'", ОбщегоНазначения.КодОсновногоЯзыка()),
			УровеньЖурналаРегистрации.Ошибка, , , Результат.ПодробноеПредставлениеОшибки);
		Результат.Статус = "Ошибка";
		Возврат Результат;
	КонецЕсли;
	
	ЗаписатьОстаткиСообщенийПользователю(ИдентификаторЗадания);
	
	Если Задание.Состояние = СостояниеФоновогоЗадания.Активно
	 Или ВыполненПерезапускУправляющегоПотока(ИдентификаторЗадания, Задание) Тогда
		Возврат Результат;
	КонецЕсли;
	
	Если Задание.Состояние = СостояниеФоновогоЗадания.Отменено Тогда
		УстановитьПривилегированныйРежим(Истина);
		Если ПараметрыСеанса.ДлительныеОперации.Отмененные.Найти(ПоследнийИдентификатор) = Неопределено Тогда
			Результат.Статус = "Ошибка";
			Если Задание.ИнформацияОбОшибке <> Неопределено Тогда
				Результат.КраткоеПредставлениеОшибки   = НСтр("ru = 'Операция отменена администратором.'");
				Результат.ПодробноеПредставлениеОшибки = Результат.КраткоеПредставлениеОшибки;
			КонецЕсли;
		Иначе
			Результат.Статус = "Отменено";
		КонецЕсли;
		УстановитьПривилегированныйРежим(Ложь);
		Возврат Результат;
	КонецЕсли;
	
	Если Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно 
		Или Задание.Состояние = СостояниеФоновогоЗадания.Отменено Тогда
		
		Результат.Статус = "Ошибка";
		Если Задание.ИнформацияОбОшибке <> Неопределено Тогда
			УстановитьСвойстваОшибки(Результат, Задание.ИнформацияОбОшибке);
		КонецЕсли;
		Возврат Результат;
	КонецЕсли;
	
	Результат.Статус = "Выполнено";
	Возврат Результат;
	
КонецФункции

Процедура ВыполнитьПроцедуруМодуляОбъектаОбработки(Параметры, АдресХранилища) Экспорт 
	
	Если БезопасныйРежим() <> Ложь Тогда
		БезопасныйРежим = БезопасныйРежим();
	ИначеЕсли Параметры.Свойство("БезопасныйРежим") И Параметры.БезопасныйРежим <> Ложь Тогда
		БезопасныйРежим = Параметры.БезопасныйРежим;
	Иначе
		БезопасныйРежим = Ложь;
	КонецЕсли;
	
	Если Параметры.ЭтоВнешняяОбработка Тогда
		Ссылка = ОбщегоНазначенияКлиентСервер.СвойствоСтруктуры(Параметры, "ДополнительнаяОбработкаСсылка");
		Если ЗначениеЗаполнено(Ссылка) И ОбщегоНазначения.ПодсистемаСуществует("СтандартныеПодсистемы.ДополнительныеОтчетыИОбработки") Тогда
			Обработка = ОбщегоНазначения.ОбщийМодуль("ДополнительныеОтчетыИОбработки").ОбъектВнешнейОбработки(Ссылка);
		Иначе
			ВыполнитьПроверкуПравДоступа("ИнтерактивноеОткрытиеВнешнихОбработок", Метаданные);
			Обработка = ВнешниеОбработки.Создать(Параметры.ИмяОбработки, БезопасныйРежим);
		КонецЕсли;
	Иначе
		Обработка = Обработки[Параметры.ИмяОбработки].Создать();
	КонецЕсли;
	
	Если БезопасныйРежим() = Ложь И БезопасныйРежим <> Ложь Тогда
		УстановитьБезопасныйРежим(БезопасныйРежим);
	КонецЕсли;
	
	Попытка
		ПолноеИмяПроцедуры = Обработка.Метаданные().ПолноеИмя() + "." + Параметры.ИмяМетода;
	Исключение
		ПолноеИмяПроцедуры = Параметры.ИмяМетода;
	КонецПопытки;
	
	УстановитьПолноеИмяПрикладнойПроцедуры(ПолноеИмяПроцедуры);
	
	ПараметрыМетода = Новый Массив;
	ПараметрыМетода.Добавить(Параметры.ПараметрыВыполнения);
	ПараметрыМетода.Добавить(АдресХранилища);
	ОбщегоНазначения.ВыполнитьМетодОбъекта(Обработка, Параметры.ИмяМетода, ПараметрыМетода);
	
КонецПроцедуры

Процедура ВыполнитьПроцедуруМодуляОбъектаОтчета(Параметры, АдресХранилища) Экспорт
	
	Если БезопасныйРежим() <> Ложь Тогда
		БезопасныйРежим = БезопасныйРежим();
	ИначеЕсли Параметры.Свойство("БезопасныйРежим") И Параметры.БезопасныйРежим <> Ложь Тогда
		БезопасныйРежим = Параметры.БезопасныйРежим;
	Иначе
		БезопасныйРежим = Ложь;
	КонецЕсли;
	
	Если Параметры.ЭтоВнешнийОтчет Тогда
		ВыполнитьПроверкуПравДоступа("ИнтерактивноеОткрытиеВнешнихОтчетов", Метаданные);
		Отчет = ВнешниеОтчеты.Создать(Параметры.ИмяОтчета, БезопасныйРежим);
	Иначе
		Отчет = Отчеты[Параметры.ИмяОтчета].Создать();
	КонецЕсли;
	
	Если БезопасныйРежим() = Ложь И БезопасныйРежим <> Ложь Тогда
		УстановитьБезопасныйРежим(БезопасныйРежим);
	КонецЕсли;
	
	Попытка
		ПолноеИмяПроцедуры = Отчет.Метаданные().ПолноеИмя() + "." + Параметры.ИмяМетода;
	Исключение
		ПолноеИмяПроцедуры = Параметры.ИмяМетода;
	КонецПопытки;
	
	УстановитьПолноеИмяПрикладнойПроцедуры(ПолноеИмяПроцедуры);
	
	ПараметрыМетода = Новый Массив;
	ПараметрыМетода.Добавить(Параметры.ПараметрыВыполнения);
	ПараметрыМетода.Добавить(АдресХранилища);
	ОбщегоНазначения.ВыполнитьМетодОбъекта(Отчет, Параметры.ИмяМетода, ПараметрыМетода);
	
КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Обработчики событий подсистем конфигурации.

// Параметры:
//  ИмяПараметра - Строка
//  УстановленныеПараметры - Массив из Строка
//
Процедура УстановкаПараметровСеанса(ИмяПараметра, УстановленныеПараметры) Экспорт
	
	Если ИмяПараметра = "ДлительныеОперации" Тогда
		Свойства = Новый Структура;
		Свойства.Вставить("Отмененные", Новый ФиксированныйМассив(Новый Массив));
		Свойства.Вставить("Перезапущенные", Новый ФиксированноеСоответствие(Новый Соответствие));
		Свойства.Вставить("ИдентификаторОсновногоЗадания");
		Свойства.Вставить("ПолученныеОповещения", Новый ФиксированноеСоответствие(Новый Соответствие));
		ПараметрыСеанса.ДлительныеОперации = Новый ФиксированнаяСтруктура(Свойства);
		УстановленныеПараметры.Добавить("ДлительныеОперации");
	КонецЕсли;
	
КонецПроцедуры

// См. ОбщегоНазначенияПереопределяемый.ПриДобавленииСерверныхОповещений
Процедура ПриДобавленииСерверныхОповещений(Оповещения) Экспорт
	
	Оповещение = СерверныеОповещения.НовоеСерверноеОповещение(ИмяОповещения());
	Оповещение.ИмяМодуляОтправки  = "";
	Оповещение.ИмяМодуляПолучения = "ДлительныеОперацииКлиент";
	Оповещения.Вставить(Оповещение.Имя, Оповещение);
	
	Оповещение = СерверныеОповещения.НовоеСерверноеОповещение(ИмяДополнительногоОповещения());
	Оповещение.ИмяМодуляОтправки  = "ДлительныеОперации";
	Оповещение.ИмяМодуляПолучения = "ДлительныеОперацииКлиент";
	Оповещения.Вставить(Оповещение.Имя, Оповещение);
	
КонецПроцедуры

// Возвращает допустимое количество потоков на все многопоточные длительные операции
// с учетом управляющих потоков.
//
// Если допустимое количество потоков 10, а операций 2,
// то получится 2 управляющих потока и 8 исполняющих потоков.
//
// Если допустимое количество потоков 3, а операций 2,
// то получится 2 управляющих потока и 1 исполняющий поток
// причем один управляющий поток будет выполнять функцию исполняющего.
//
// Если допустимое количество потоков 5, а операций 7,
// то получится 7 управляющих потоков и 0 исполняющих потоков
// причем каждый управляющий поток будет выполнять функцию исполняющего.
//
// Соответственно, если допустимое количество 1,
// то получится, что исполняющих потоков всегда будет 0
// и каждый управляющий поток будет выполнять функцию исполняющего.
//
// Возвращаемое значение:
//  Число - количество потоков.
//
Функция ДопустимоеКоличествоПотоков() Экспорт
	
	Если ОбщегоНазначения.РазделениеВключено()
	 Или ОбщегоНазначения.ИнформационнаяБазаФайловая() Тогда
		Возврат 1;
	КонецЕсли;
	
	ДопустимоеКоличествоПотоков = Константы.КоличествоПотоковДлительныхОпераций.Получить();
	
	Если ДопустимоеКоличествоПотоков > 0 Тогда
		Возврат ДопустимоеКоличествоПотоков;
	КонецЕсли;
	
	// Значение по умолчанию. Управляющий поток + 3 дочерних
	Возврат 4;
	
КонецФункции

#КонецОбласти

#Область СлужебныеПроцедурыИФункции

// См. СтандартныеПодсистемыСервер.ПриОтправкеСерверногоОповещения
Процедура ПриОтправкеСерверногоОповещения(ИмяОповещения, ВариантыПараметров) Экспорт
	
	Если ИмяОповещения <> ИмяДополнительногоОповещения() Тогда
		Возврат;
	КонецЕсли;
	
	УдалитьНесуществующиеПотоки();
	
КонецПроцедуры

Функция НовыйРезультатВыполненияОперации() Экспорт
	
	Результат = Новый Структура;
	Результат.Вставить("Статус", "Выполняется");
	Результат.Вставить("КраткоеПредставлениеОшибки", "");
	Результат.Вставить("ПодробноеПредставлениеОшибки", "");
	Результат.Вставить("Прогресс", Неопределено);
	Результат.Вставить("Сообщения", Неопределено);
	
	Возврат Результат;
	
КонецФункции

// Параметры:
//  ИдентификаторЗадания - УникальныйИдентификатор
//
// Возвращаемое значение:
//  УникальныйИдентификатор
//
Функция ПоследнийИдентификатор(ИдентификаторЗадания)
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	Последний = ПараметрыСеанса.ДлительныеОперации.Перезапущенные.Получить(ИдентификаторЗадания);
	
	УстановитьПривилегированныйРежим(Ложь);
	УстановитьОтключениеБезопасногоРежима(Ложь);
	
	Если Последний = Неопределено Тогда
		Возврат ИдентификаторЗадания;
	КонецЕсли;
	
	Возврат Последний;
	
КонецФункции

Функция ИдентификаторОсновногоЗадания(ФоновоеЗадание, ИдентификаторПроцесса = Неопределено)
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	Если ЗначениеЗаполнено(ИдентификаторПроцесса) Тогда
		Идентификатор = ПервыйИдентификаторЗаданияУправляющегоПотока(ИдентификаторПроцесса);
		Свойства = Новый Структура(ПараметрыСеанса.ДлительныеОперации);
		Свойства.ИдентификаторОсновногоЗадания = Идентификатор;
		ПараметрыСеанса.ДлительныеОперации = Новый ФиксированнаяСтруктура(Свойства);
	Иначе
		Идентификатор = ПараметрыСеанса.ДлительныеОперации.ИдентификаторОсновногоЗадания;
		Если Не ЗначениеЗаполнено(Идентификатор) Тогда
			Идентификатор = ФоновоеЗадание.УникальныйИдентификатор;
		КонецЕсли;
	КонецЕсли;
	
	УстановитьПривилегированныйРежим(Ложь);
	УстановитьОтключениеБезопасногоРежима(Ложь);
	
	Возврат Идентификатор;
	
КонецФункции

// Параметры:
//  Свойства - Структура:
//   * КраткоеПредставлениеОшибки
//   * ПодробноеПредставлениеОшибки
//  Ошибка - ИнформацияОбОшибке
//
Процедура УстановитьСвойстваОшибки(Свойства, Ошибка)
	
	КраткоеПредставлениеОшибки = ОбработкаОшибок.КраткоеПредставлениеОшибки(Ошибка);
	ПодробноеПредставлениеОшибки = ОбработкаОшибок.ПодробноеПредставлениеОшибки(Ошибка);
	
	Свойства.КраткоеПредставлениеОшибки = ОбщегоНазначенияКлиентСервер.ЗаменитьНедопустимыеСимволыXML(
		КраткоеПредставлениеОшибки);
	
	Свойства.ПодробноеПредставлениеОшибки = ОбщегоНазначенияКлиентСервер.ЗаменитьНедопустимыеСимволыXML(
		ПодробноеПредставлениеОшибки);
	
КонецПроцедуры

// Конструктор параметров многопоточной операции.
// 
// Параметры:
//   ИдентификаторПроцесса - УникальныйИдентификатор
//   СохраненныеПараметры - Неопределено, Структура
// 
// Возвращаемое значение:
//  Структура:
//   * ИдентификаторПроцесса - УникальныйИдентификатор
//   * ИмяМетода - Строка
//   * ДляФункции - Булево
//   * ПараметрыВыполнения - см. ПараметрыВыполненияФункции
//   * ПараметрыМетода - Число, Структура
//   * АдресРезультатов - Строка
//
Функция ПараметрыМногопоточнойОперации(ИдентификаторПроцесса, СохраненныеПараметры = Неопределено) 
	
	ПараметрыМногопоточнойОперации = Новый Структура;
	ПараметрыМногопоточнойОперации.Вставить("ИдентификаторПроцесса", ИдентификаторПроцесса);
	ПараметрыМногопоточнойОперации.Вставить("ИмяМетода",             "");
	ПараметрыМногопоточнойОперации.Вставить("ДляФункции",            Ложь);
	ПараметрыМногопоточнойОперации.Вставить("ПараметрыВыполнения",   ПараметрыВыполненияФункции(Неопределено));
	ПараметрыМногопоточнойОперации.Вставить("ПараметрыМетода",       0);
	ПараметрыМногопоточнойОперации.Вставить("АдресРезультатов",      "");
	
	Если ТипЗнч(СохраненныеПараметры) = Тип("Структура") Тогда
		ЗаполнитьЗначенияСвойств(ПараметрыМногопоточнойОперации, СохраненныеПараметры);
	КонецЕсли;
	
	Возврат ПараметрыМногопоточнойОперации;
	
КонецФункции

// См. ОбщегоНазначенияПереопределяемый.ПриПериодическомПолученииДанныхКлиентаНаСервере
Процедура ПриПериодическомПолученииДанныхКлиентаНаСервере(Параметры, Результаты) Экспорт
	
	ПараметрыПроверки = Параметры.Получить( // см. ДлительныеОперацииКлиент.ПараметрыПроверкиДлительныхОпераций
		"СтандартныеПодсистемы.БазоваяФункциональность.ПараметрыПроверкиДлительныхОпераций");
	
	Если ПараметрыПроверки = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	Результаты.Вставить("СтандартныеПодсистемы.БазоваяФункциональность.РезультатПроверкиДлительныхОпераций",
		РезультатПроверкиДлительныхОпераций(ПараметрыПроверки));
	
КонецПроцедуры

// Параметры:
//  Параметры - см. ДлительныеОперацииКлиент.ПараметрыПроверкиДлительныхОпераций
//
// Возвращаемое значение:
//  Соответствие из КлючИЗначение:
//   * Ключ     - УникальныйИдентификатор - идентификатор задания
//   * Значение - см. ОперацияВыполнена
//  
Функция РезультатПроверкиДлительныхОпераций(Параметры) Экспорт
	
	Результат = Новый Соответствие;
	Для Каждого ИдентификаторЗадания Из Параметры.ЗаданияДляПроверки Цикл
		// @skip-check query-in-loop - ветка с запросом вызывается редко (только при перезапуске управляющего потока)
		Результат.Вставить(ИдентификаторЗадания, ОперацияВыполнена(ИдентификаторЗадания));
	КонецЦикла;
	
	Для Каждого ИдентификаторЗадания Из Параметры.ЗаданияДляОтмены Цикл
		ОтменитьВыполнениеЗадания(ИдентификаторЗадания);
		Результат.Вставить(ИдентификаторЗадания, Новый Структура("Статус", "Отменено"));
	КонецЦикла;
	
	Возврат Результат;
	
КонецФункции

Функция ЗапуститьФоновоеЗаданиеСКонтекстомКлиента(ИмяПроцедуры,
			ПараметрыВыполнения, ПараметрыПроцедуры = Неопределено, БезопасныйРежим = Ложь, ОтправлятьОповещения = Ложь) Экспорт
	
	КлючФоновогоЗадания = ПараметрыВыполнения.КлючФоновогоЗадания;
	НаименованиеФоновогоЗадания = ?(ПустаяСтрока(ПараметрыВыполнения.НаименованиеФоновогоЗадания),
		ИмяПроцедуры, ПараметрыВыполнения.НаименованиеФоновогоЗадания);
	
	ПараметрыКлиента = СтандартныеПодсистемыСервер.ПараметрыКлиентаНаСервере(Ложь);
	Если ОтправлятьОповещения И Не ЗначениеЗаполнено(ПараметрыКлиента.Получить("КлючСеансаРодителя")) Тогда
		ПараметрыКлиента = Новый Соответствие(ПараметрыКлиента);
		ПараметрыКлиента.Вставить("КлючСеансаРодителя", СерверныеОповещения.КлючСеанса());
		ПараметрыКлиента = Новый ФиксированноеСоответствие(ПараметрыКлиента);
	КонецЕсли;
	
	ВсеПараметры = Новый Структура;
	ВсеПараметры.Вставить("ИмяПроцедуры",       ИмяПроцедуры);
	ВсеПараметры.Вставить("ПараметрыПроцедуры", ПараметрыПроцедуры);
	ВсеПараметры.Вставить("ПараметрыКлиентаНаСервере", ПараметрыКлиента);
	ВсеПараметры.Вставить("ПараметрыВыполнения", ПараметрыВыполнения);
	ВсеПараметры.Вставить("БезопасныйРежим",     БезопасныйРежим);
	
	Если Не ПараметрыВыполнения.БезРасширений
		И Не ПараметрыВыполнения.СРасширениямиБазыДанных Тогда		
		Справочники.ВерсииРасширений.ВставитьЗарегистрированныйСоставУстановленныхРасширений(ВсеПараметры);
	КонецЕсли;
	
	ПараметрыПроцедурыФоновогоЗадания = Новый Массив;
	ПараметрыПроцедурыФоновогоЗадания.Добавить(ВсеПараметры);
	
	ИмяПроцедурыФоновогоЗадания = ИмяПроцедурыФоновогоЗаданияДлительнойОперации();
	
	Возврат ВыполнитьФоновоеЗадание(ПараметрыВыполнения,
		ИмяПроцедурыФоновогоЗадания, ПараметрыПроцедурыФоновогоЗадания,
		КлючФоновогоЗадания, НаименованиеФоновогоЗадания);
	
КонецФункции

// Возвращает Истина, если текущий сеанс является сеансом фонового задания
// длительной операции или имя метода фонового задания не указано
// ни в одном из регламентных заданий.
//
Функция ПропуститьОбработчикПередЗапускомПрограммы() Экспорт
	
	Если ТекущийРежимЗапуска() <> Неопределено Тогда
		Возврат Ложь;
	КонецЕсли;
	
	ФоновоеЗадание = ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание();
	Если ФоновоеЗадание = Неопределено Тогда
		Возврат Ложь;
	КонецЕсли;
	
	ИмяМетода = НРег(ФоновоеЗадание.ИмяМетода);
	Если ИмяМетода = НРег(ИмяПроцедурыФоновогоЗаданияДлительнойОперации()) Тогда
		Возврат Истина;
	КонецЕсли;
	
	Для Каждого РегламентноеЗадание Из Метаданные.РегламентныеЗадания Цикл
		Если ИмяМетода = НРег(РегламентноеЗадание.ИмяМетода) Тогда
			Возврат Ложь;
		КонецЕсли;
	КонецЦикла;
	
	Возврат Истина;
	
КонецФункции

Функция ИмяПроцедурыФоновогоЗаданияДлительнойОперации()
	
	Возврат "ДлительныеОперации.ВыполнитьСКонтекстомКлиента";
	
КонецФункции

// Возвращаемое значение:
//  Строка
//
Функция ПолноеИмяПрикладнойПроцедурыДлительнойОперации() Экспорт
	
	Если Не СтандартныеПодсистемыПовтИсп.ЭтоСеансДлительнойОперации() Тогда
		Возврат "";
	КонецЕсли;
	
	ПолноеИмя = СтандартныеПодсистемыСервер.ПараметрыКлиентаНаСервере(Ложь).Получить(
		"ПолноеИмяПрикладнойПроцедурыДлительнойОперации");
	
	Возврат Строка(ПолноеИмя);
	
КонецФункции

Процедура УстановитьПолноеИмяПрикладнойПроцедуры(ПолноеИмяПроцедуры)
	
	Если Не СтандартныеПодсистемыПовтИсп.ЭтоСеансДлительнойОперации() Тогда
		Возврат;
	КонецЕсли;
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	ПараметрыКлиента = Новый Соответствие(ПараметрыСеанса.ПараметрыКлиентаНаСервере);
	ПараметрыКлиента.Вставить("ПолноеИмяПрикладнойПроцедурыДлительнойОперации", ПолноеИмяПроцедуры);
	ПараметрыСеанса.ПараметрыКлиентаНаСервере = Новый ФиксированноеСоответствие(ПараметрыКлиента);
	УстановитьПривилегированныйРежим(Ложь);
	УстановитьОтключениеБезопасногоРежима(Ложь);
	
КонецПроцедуры

// Продолжение процедуры ЗапуститьФоновоеЗаданиеСКонтекстомКлиента.
Процедура ВыполнитьСКонтекстомКлиента(ВсеПараметры) Экспорт
	
	ПараметрыКлиента = ВсеПараметры.ПараметрыКлиентаНаСервере;
	Если ЗначениеЗаполнено(ПараметрыКлиента.Получить("КлючСеансаРодителя"))
	   И Не ЗначениеЗаполнено(ПараметрыКлиента.Получить("ИдентификаторЗаданияМногопоточногоПроцесса")) Тогда
		
		ФоновоеЗадание = ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание();
		Если ФоновоеЗадание <> Неопределено
		   И ВсеПараметры.ИмяПроцедуры = ИмяМетодаМногопоточногоПроцесса() Тогда
			ПараметрыКлиента = Новый Соответствие(ПараметрыКлиента);
			ИдентификаторПроцесса = ВсеПараметры.ПараметрыПроцедуры[0].ИдентификаторПроцесса;
			ПараметрыКлиента.Вставить("ИдентификаторЗаданияМногопоточногоПроцесса",
				ИдентификаторОсновногоЗадания(ФоновоеЗадание, ИдентификаторПроцесса));
			ПараметрыКлиента = Новый ФиксированноеСоответствие(ПараметрыКлиента);
		КонецЕсли;
	КонецЕсли;
	
	УстановитьПривилегированныйРежим(Истина);
	ПараметрыСеанса.ПараметрыКлиентаНаСервере = ПараметрыКлиента;
	Справочники.ВерсииРасширений.ВосстановитьЗарегистрированныйСоставУстановленныхРасширений(ВсеПараметры);
	Справочники.ВерсииРасширений.ЗарегистрироватьИспользованиеВерсииРасширений();
	УстановитьПривилегированныйРежим(Ложь);
	
	Если БезопасныйРежим() = Ложь И ВсеПараметры.БезопасныйРежим <> Ложь Тогда
		Если ВРег(ВсеПараметры.ИмяПроцедуры) = ВРег("ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОбработки")
		 Или ВРег(ВсеПараметры.ИмяПроцедуры) = ВРег("ДлительныеОперации.ВыполнитьПроцедуруМодуляОбъектаОтчета") Тогда
			
			ВсеПараметры.ПараметрыПроцедуры[0].Вставить("БезопасныйРежим", ВсеПараметры.БезопасныйРежим);
		Иначе
			УстановитьБезопасныйРежим(ВсеПараметры.БезопасныйРежим);
		КонецЕсли;
	КонецЕсли;
	
	УстановитьПолноеИмяПрикладнойПроцедуры(ВсеПараметры.ИмяПроцедуры);
	Результат = НовыйРезультатВыполненияОперации();
	Попытка
		ПараметрыВыполнения = ВсеПараметры.ПараметрыВыполнения;
		Если ПараметрыВыполнения.Свойство("ЭтоФункция") И ПараметрыВыполнения.ЭтоФункция Тогда
			ВызватьФункцию(ВсеПараметры.ИмяПроцедуры, ВсеПараметры.ПараметрыПроцедуры, ПараметрыВыполнения);
		Иначе
			ВызватьПроцедуру(ВсеПараметры.ИмяПроцедуры, ВсеПараметры.ПараметрыПроцедуры, ПараметрыВыполнения);
		КонецЕсли;
		Результат.Статус = "Выполнено";
	Исключение
		Результат.Статус = "Ошибка";
		УстановитьСвойстваОшибки(Результат, ИнформацияОбОшибке());
		УстановитьПолноеИмяПрикладнойПроцедуры(ИмяПроцедурыФоновогоЗаданияДлительнойОперации());
		ОтправитьОповещениеКлиенту("ДлительнаяОперацияЗавершена", Результат);
		ВызватьИсключение;
	КонецПопытки;
	
	УстановитьПолноеИмяПрикладнойПроцедуры(ИмяПроцедурыФоновогоЗаданияДлительнойОперации());
	ОтправитьОповещениеКлиенту("ДлительнаяОперацияЗавершена", Результат);
	
КонецПроцедуры

Процедура ВызватьПроцедуру(ИмяПроцедуры, ПараметрыВызова, ПараметрыВыполнения)
	
	ЧастиИмени = СтрРазделить(ИмяПроцедуры, ".");
	ЭтоПроцедураМодуляОбработки = (ЧастиИмени.Количество() = 4) И ВРег(ЧастиИмени[2]) = "МОДУЛЬОБЪЕКТА";
	Если Не ЭтоПроцедураМодуляОбработки Тогда
		ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяПроцедуры, ПараметрыВызова);
		Возврат;
	КонецЕсли;
	
	ЭтоОбработка = ВРег(ЧастиИмени[0]) = "ОБРАБОТКА";
	ЭтоОтчет = ВРег(ЧастиИмени[0]) = "ОТЧЕТ";
	Если ЭтоОбработка Или ЭтоОтчет Тогда
		МенеджерОбъекта = ?(ЭтоОтчет, Отчеты, Обработки);
		ОбработкаОтчетОбъект = МенеджерОбъекта[ЧастиИмени[1]].Создать();
		ОбщегоНазначения.ВыполнитьМетодОбъекта(ОбработкаОтчетОбъект, ЧастиИмени[3], ПараметрыВызова);
		Возврат;
	КонецЕсли;
	
	ЭтоВнешняяОбработка = ВРег(ЧастиИмени[0]) = "ВНЕШНЯЯОБРАБОТКА";
	ЭтоВнешнийОтчет = ВРег(ЧастиИмени[0]) = "ВНЕШНИЙОТЧЕТ";
	Если ЭтоВнешняяОбработка Или ЭтоВнешнийОтчет Тогда
		ОбработкаОтчетОбъект = ВнешняяОбработкаОтчетОбъект(ЭтоВнешнийОтчет, ПараметрыВыполнения, ЧастиИмени[1]);
		ОбщегоНазначения.ВыполнитьМетодОбъекта(ОбработкаОтчетОбъект, ЧастиИмени[3], ПараметрыВызова);
		Возврат;
	КонецЕсли;
	
	ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
		НСтр("ru = 'Неверный формат параметра %2 (переданное значение: %1)'"), ИмяПроцедуры, "ИмяПроцедуры");
	
КонецПроцедуры

Функция ВнешняяОбработкаОтчетОбъект(ЭтоВнешнийОтчет, ПараметрыВыполнения, ИмяПодключеннойОбработкиОтчета)
	
	МенеджерОбъекта = ?(ЭтоВнешнийОтчет, ВнешниеОтчеты, ВнешниеОбработки);
	
	Если ТипЗнч(ПараметрыВыполнения.ВнешнийОтчетОбработка) <> Тип("ДвоичныеДанные") Тогда
		Если ПараметрыВыполнения.ЗапуститьНеВФоне Тогда
			Возврат МенеджерОбъекта.Создать(ИмяПодключеннойОбработкиОтчета, БезопасныйРежим());
		Иначе
			ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'Для вызова процедуры внешнего отчета или внешней обработки
				           |требуется заполнить параметр %1'"),
				"ВнешнийОтчетОбработка");
			ВызватьИсключение ТекстОшибки;
		КонецЕсли;
	КонецЕсли;
	
	Если ЭтоВнешнийОтчет Тогда
		ВыполнитьПроверкуПравДоступа("ИнтерактивноеОткрытиеВнешнихОтчетов", Метаданные);
	Иначе
		ВыполнитьПроверкуПравДоступа("ИнтерактивноеОткрытиеВнешнихОбработок", Метаданные);
	КонецЕсли;
	
	БезопасныйРежим = БезопасныйРежим();
	Если БезопасныйРежим <> Ложь Тогда
		УстановитьОтключениеБезопасногоРежима(Истина);
	КонецЕсли;
	
	ИмяВременногоФайлаОбработкиОтчета = ПолучитьИмяВременногоФайла();
	ПараметрыВыполнения.ВнешнийОтчетОбработка.Записать(ИмяВременногоФайлаОбработкиОтчета);
	
	Попытка
		ОбработкаОтчетОбъект = МенеджерОбъекта.Создать(ИмяВременногоФайлаОбработкиОтчета, БезопасныйРежим);
	Исключение
		УдалитьФайлы(ИмяВременногоФайлаОбработкиОтчета);
		Если БезопасныйРежим <> Ложь Тогда
			УстановитьОтключениеБезопасногоРежима(Ложь);
		КонецЕсли;
		ВызватьИсключение;
	КонецПопытки;
	УдалитьФайлы(ИмяВременногоФайлаОбработкиОтчета);
	Если БезопасныйРежим <> Ложь Тогда
		УстановитьОтключениеБезопасногоРежима(Ложь);
	КонецЕсли;
	
	Возврат ОбработкаОтчетОбъект;
	
КонецФункции

Процедура ВызватьФункцию(ИмяФункции, ПараметрыПроцедуры, ПараметрыВыполнения)
	
	ЧастиИмени = СтрРазделить(ИмяФункции, ".");
	ЭтоПроцедураМодуляОбработки = (ЧастиИмени.Количество() = 4) И ВРег(ЧастиИмени[2]) = "МОДУЛЬОБЪЕКТА";
	Если Не ЭтоПроцедураМодуляОбработки Тогда
		Результат = ОбщегоНазначения.ВызватьФункциюКонфигурации(ИмяФункции, ПараметрыПроцедуры);
		УстановитьРезультатВызоваФункции(Результат, ПараметрыВыполнения);
		Возврат;
	КонецЕсли;
	
	ЭтоОбработка = ВРег(ЧастиИмени[0]) = "ОБРАБОТКА";
	ЭтоОтчет = ВРег(ЧастиИмени[0]) = "ОТЧЕТ";
	Если ЭтоОбработка Или ЭтоОтчет Тогда
		МенеджерОбъекта = ?(ЭтоОтчет, Отчеты, Обработки);
		ОбработкаОтчетОбъект = МенеджерОбъекта[ЧастиИмени[1]].Создать();
		Результат = ОбщегоНазначения.ВызватьФункциюОбъекта(ОбработкаОтчетОбъект, ЧастиИмени[3], ПараметрыПроцедуры);
		УстановитьРезультатВызоваФункции(Результат, ПараметрыВыполнения);
		Возврат;
	КонецЕсли;
	
	ЭтоВнешняяОбработка = ВРег(ЧастиИмени[0]) = "ВНЕШНЯЯОБРАБОТКА";
	ЭтоВнешнийОтчет = ВРег(ЧастиИмени[0]) = "ВНЕШНИЙОТЧЕТ";
	Если ЭтоВнешняяОбработка Или ЭтоВнешнийОтчет Тогда
		ОбработкаОтчетОбъект = ВнешняяОбработкаОтчетОбъект(ЭтоВнешнийОтчет, ПараметрыВыполнения, ЧастиИмени[1]);
		Результат = ОбщегоНазначения.ВызватьФункциюОбъекта(ОбработкаОтчетОбъект, ЧастиИмени[3], ПараметрыПроцедуры);
		УстановитьРезультатВызоваФункции(Результат, ПараметрыВыполнения);
		Возврат;
	КонецЕсли;
	
	ВызватьИсключение СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
		НСтр("ru = 'Неверный формат параметра %2 (переданное значение: %1)'"), ИмяФункции, "ИмяФункции");
	
КонецПроцедуры

Процедура УстановитьРезультатВызоваФункции(Результат, ПараметрыВыполнения)
	
	Если Не ПараметрыВыполнения.Свойство("СвойстваИсполняющегоПотокаМногопоточнойДлительнойОперации") Тогда
		ПоместитьВоВременноеХранилище(Результат, ПараметрыВыполнения.АдресРезультата);
		Возврат;
	КонецЕсли;
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	УстановитьРезультатПотока(ПараметрыВыполнения.СвойстваИсполняющегоПотокаМногопоточнойДлительнойОперации, Результат);
	
	УстановитьПривилегированныйРежим(Ложь);
	УстановитьОтключениеБезопасногоРежима(Ложь);
	
КонецПроцедуры

// Параметры:
//  ИдентификаторЗадания - УникальныйИдентификатор, Строка
//
// Возвращаемое значение:
//  ФоновоеЗадание, Неопределено
//
Функция НайтиЗаданиеПоИдентификатору(Знач ИдентификаторЗадания)
	
	Если ТипЗнч(ИдентификаторЗадания) = Тип("Строка") Тогда
		ИдентификаторЗадания = Новый УникальныйИдентификатор(ИдентификаторЗадания);
	КонецЕсли;
	
	Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторЗадания);
	Возврат Задание;
	
КонецФункции

Функция ПолучитьИзОповещений(ПропускатьПолученные, ИдентификаторЗадания, ВидОповещений)
	
	ЗаписатьОстаткиСообщенийПользователю(ИдентификаторЗадания);
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	ПоследнееОповещение = Неопределено;
	Если ПропускатьПолученные Тогда
		ПоследниеОповещения = ПараметрыСеанса.ДлительныеОперации.ПолученныеОповещения.Получить(ИдентификаторЗадания);
		Если ПоследниеОповещения <> Неопределено Тогда
			ПоследнееОповещение = ПоследниеОповещения[ВидОповещений];
		КонецЕсли;
	КонецЕсли;
	Оповещения = СерверныеОповещения.СерверныеОповещенияДляКлиента(ИдентификаторЗадания,
		ИдентификаторВидаОповещения(ВидОповещений), ПоследнееОповещение);
	
	УстановитьПривилегированныйРежим(Ложь);
	УстановитьОтключениеБезопасногоРежима(Ложь);
	
	СообщенияОповещений = Новый Массив;
	Если ВидОповещений = "Сообщения" Тогда
		Результат = Новый ФиксированныйМассив(СообщенияОповещений);
	Иначе
		Результат = Неопределено;
	КонецЕсли;
	
	Для Каждого Оповещение Из Оповещения Цикл
		Параметры = Оповещение.Содержимое.Результат;
		Если ТипЗнч(Параметры) <> Тип("Структура")
		 Или Не Параметры.Свойство("Результат")
		 Или ТипЗнч(Параметры.Результат) <> Тип("Структура") Тогда
			Продолжить;
		КонецЕсли;
		Если ВидОповещений = "ДлительнаяОперацияЗавершена"
		   И Параметры.Свойство("ВидОповещения")
		   И Параметры.ВидОповещения = ВидОповещений Тогда
			Возврат Параметры.Результат;
		КонецЕсли;
		Если Не Параметры.Результат.Свойство(ВидОповещений)
		 Или ВидОповещений = "Сообщения"
		   И ТипЗнч(Параметры.Результат[ВидОповещений]) <> Тип("ФиксированныйМассив")
		 Или ВидОповещений = "Прогресс"
		   И ТипЗнч(Параметры.Результат[ВидОповещений]) <> Тип("Структура") Тогда
			Продолжить;
		КонецЕсли;
		Если ВидОповещений = "Сообщения" Тогда
			Для Каждого Сообщение Из Параметры.Результат.Сообщения Цикл
				СообщенияОповещений.Добавить(Сообщение)
			КонецЦикла;
			Результат = Новый ФиксированныйМассив(СообщенияОповещений);
		Иначе
			Результат = Параметры.Результат.Прогресс;
		КонецЕсли;
	КонецЦикла;
	
	Если ПропускатьПолученные И ЗначениеЗаполнено(Оповещение) Тогда
		Оповещение.Удалить("Содержимое");
		УстановитьОтключениеБезопасногоРежима(Истина);
		УстановитьПривилегированныйРежим(Истина);
		Свойства = Новый Структура(ПараметрыСеанса.ДлительныеОперации);
		ПолученныеОповещения = Новый Соответствие(Свойства.ПолученныеОповещения);
		ПоследниеОповещения = ПолученныеОповещения.Получить(ИдентификаторЗадания);
		Если ПоследниеОповещения = Неопределено Тогда
			ПоследниеОповещения = Новый Структура("Сообщения, Прогресс");
		Иначе
			ПоследниеОповещения = Новый Структура(ПоследниеОповещения);
		КонецЕсли;
		ПоследниеОповещения[ВидОповещений] = Новый ФиксированнаяСтруктура(Оповещение);
		ПолученныеОповещения.Вставить(ИдентификаторЗадания, Новый ФиксированнаяСтруктура(ПоследниеОповещения));
		КлючиУстаревших = Новый Массив;
		Для Каждого КлючИЗначение Из ПолученныеОповещения Цикл
			ПолученноеОповещение = ?(КлючИЗначение.Значение.Сообщения = Неопределено,
				КлючИЗначение.Значение.Прогресс, КлючИЗначение.Значение.Сообщения);
			Если ПолученноеОповещение.ДатаДобавления + 60*60 < ТекущаяДатаСеанса() Тогда
				КлючиУстаревших.Добавить(КлючИЗначение.Ключ);
			КонецЕсли;
		КонецЦикла;
		Для Каждого Ключ Из КлючиУстаревших Цикл
			ПолученныеОповещения.Удалить(Ключ);
		КонецЦикла;
		Свойства.ПолученныеОповещения = Новый ФиксированноеСоответствие(ПолученныеОповещения);
		ПараметрыСеанса.ДлительныеОперации = Новый ФиксированнаяСтруктура(Свойства);
		УстановитьПривилегированныйРежим(Ложь);
		УстановитьОтключениеБезопасногоРежима(Ложь);
	КонецЕсли;
	
	Возврат Результат;
	
КонецФункции

Функция ИмяОповещения()
	Возврат "СтандартныеПодсистемы.БазоваяФункциональность.ДлительныеОперации";
КонецФункции

Функция ИмяДополнительногоОповещения()
	Возврат "СтандартныеПодсистемы.БазоваяФункциональность.ДлительныеОперации.УдалениеНесуществующихПотоков";
КонецФункции

// Параметры:
//  Данные - см. СерверныеОповещения.НовыеДанныеСообщения
// 
// Возвращаемое значение:
//  Булево
//
Функция ПропуститьОповещение(Данные) Экспорт
	
	// Когда система взаимодействия не подключена
	// оповещение о завершении нужно пропускать,
	// так как оно требует дополнительного серверного
	// вызова ДлительныеОперацииВызовСервера.ФоновоеЗаданиеЗавершено.
	// При отсутствии системы взаимодействия этот контроль не нужен,
	// так как используется функция ОперацияВыполнена
	// в рамках дополнительного контроля завершения активных операций
	// через функцию РезультатПроверкиДлительныхОпераций.
	
	Возврат Данные.ИмяОповещения = ИмяОповещения()
	      И Данные.Результат.ВидОповещения = "ДлительнаяОперацияЗавершена"
	      И Не СерверныеОповещения.СистемаВзаимодействийПодключена();
	
КонецФункции

Процедура ОтправитьОповещениеКлиенту(ВидОповещения, ПередаваемоеЗначение,
			ФоновоеЗадание = Неопределено, ИдентификаторОсновногоЗадания = Неопределено) Экспорт
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	ЗаписатьСообщенияПользователю = ФоновоеЗадание <> Неопределено И ВидОповещения = "СообщениеПользователю";
	Если ЗаписатьСообщенияПользователю Тогда
		КлючСеансаРодителя = СерверныеОповещения.КлючСеанса();
	Иначе
		КлючСеансаРодителя = СтандартныеПодсистемыСервер.ПараметрыКлиентаНаСервере(Ложь).Получить(
			"КлючСеансаРодителя");
		Если Не ЗначениеЗаполнено(КлючСеансаРодителя) Тогда
			Возврат;
		КонецЕсли;
		ФоновоеЗадание = ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание();
		Если ФоновоеЗадание = Неопределено Тогда
			Возврат;
		КонецЕсли;
		ИдентификаторЗаданияМногопоточногоПроцесса =
			СтандартныеПодсистемыСервер.ПараметрыКлиентаНаСервере(Ложь).Получить(
				"ИдентификаторЗаданияМногопоточногоПроцесса");
		ИдентификаторОсновногоЗадания = ?(ЗначениеЗаполнено(ИдентификаторЗаданияМногопоточногоПроцесса),
			ИдентификаторЗаданияМногопоточногоПроцесса, ИдентификаторОсновногоЗадания(ФоновоеЗадание));
	КонецЕсли;
	
	Если ВидОповещения = "ДлительнаяОперацияЗавершена" Тогда
		Если ЗначениеЗаполнено(ИдентификаторЗаданияМногопоточногоПроцесса) Тогда
			Если ИдентификаторЗаданияМногопоточногоПроцесса <> ИдентификаторОсновногоЗадания(ФоновоеЗадание)
			 Или ПередаваемоеЗначение.Статус = "Ошибка" Тогда
				Возврат;
			КонецЕсли;
		КонецЕсли;
		Результат = ПередаваемоеЗначение;
	Иначе
		Результат = НовыйРезультатВыполненияОперации();
	КонецЕсли;
	Если ВидОповещения = "СообщениеПользователю" Тогда
		Результат.Сообщения = Новый ФиксированныйМассив(
			ОбщегоНазначенияКлиентСервер.ЗначениеВМассиве(ПередаваемоеЗначение));
	ИначеЕсли ВидОповещения = "Прогресс" Тогда
		Сообщения = ФоновоеЗадание.ПолучитьСообщенияПользователю(Истина);
		Для Каждого Сообщение Из Сообщения Цикл
			// @skip-check query-in-loop - ветка с запросом вызывается только при первом вызове в сеансе
			ОтправитьОповещениеКлиенту("СообщениеПользователю", Сообщение);
		КонецЦикла;
		Результат.Сообщения = Новый ФиксированныйМассив(Новый Массив);
		Результат.Прогресс = ПередаваемоеЗначение;
	ИначеЕсли ВидОповещения = "ДлительнаяОперацияЗавершена" Тогда
		Сообщения = ФоновоеЗадание.ПолучитьСообщенияПользователю(Истина);
		Для Каждого Сообщение Из Сообщения Цикл
			// @skip-check query-in-loop - ветка с запросом вызывается только при первом вызове в сеансе
			ОтправитьОповещениеКлиенту("СообщениеПользователю", Сообщение);
		КонецЦикла;
	КонецЕсли;
	
	ПараметрыОповещения = Новый Структура;
	ПараметрыОповещения.Вставить("ВидОповещения", ВидОповещения);
	ПараметрыОповещения.Вставить("ИдентификаторЗадания", ИдентификаторОсновногоЗадания);
	ПараметрыОповещения.Вставить("Результат", Результат);
	ПараметрыОповещения.Вставить("ВремяОтправки", ТекущаяУниверсальнаяДатаВМиллисекундах());
	
	КлючиСеансов = ОбщегоНазначенияКлиентСервер.ЗначениеВМассиве(КлючСеансаРодителя);
	Адресаты = Новый Соответствие;
	Адресаты.Вставить(ПользователиИнформационнойБазы.ТекущийПользователь().УникальныйИдентификатор, КлючиСеансов);
	
	ДополнительныеПараметрыОтправки = СерверныеОповещения.ДополнительныеПараметрыОтправки();
	ДополнительныеПараметрыОтправки.ИдентификаторГруппы  = ИдентификаторОсновногоЗадания;
	ДополнительныеПараметрыОтправки.ВидОповещенияВГруппе = ИдентификаторВидаОповещения(ВидОповещения);
	
	Если ВидОповещения = "Прогресс" Тогда
		ДополнительныеПараметрыОтправки.Заменить = Истина;
		ДополнительныеПараметрыОтправки.ОтсрочкаДоставки = 3;
		ДополнительныеПараметрыОтправки.СобытиеЖурналаПриОтсрочкеДоставки =
			НСтр("ru = 'Длительные операции.Отложенная доставка прогресса'",
				ОбщегоНазначения.КодОсновногоЯзыка());
		ДополнительныеПараметрыОтправки.КомментарийЖурналаПриОтсрочкеДоставки =
			НСтр("ru = 'Отправка прогресса чаще одного раза в 3 сек.'");
	КонецЕсли;
	
	СерверныеОповещения.ОтправитьСерверноеОповещениеСИдентификаторомГруппы(ИмяОповещения(),
		ПараметрыОповещения, Адресаты, Не ЗаписатьСообщенияПользователю, ДополнительныеПараметрыОтправки);
	
	УстановитьПривилегированныйРежим(Ложь);
	УстановитьОтключениеБезопасногоРежима(Ложь);
	
КонецПроцедуры

// Для процедуры ОтправитьОповещениеКлиенту и функции ПолучитьИзОповещений.
Функция ИдентификаторВидаОповещения(ВидОповещения)
	
	Если ВидОповещения = "СообщениеПользователю" Или ВидОповещения = "Сообщения" Тогда
		Возврат Новый УникальныйИдентификатор("0afef160-bfcb-459e-a890-a4afbb73b7ba");
	
	ИначеЕсли ВидОповещения = "Прогресс" Тогда
		Возврат Новый УникальныйИдентификатор("14076bb1-a1f5-4876-975a-3b7f69383f6c");
		
	ИначеЕсли ВидОповещения = "ДлительнаяОперацияЗавершена" Тогда
		Возврат Новый УникальныйИдентификатор("28e5ab5c-196b-44be-aab5-8fe7edb5225b");
	КонецЕсли;
	
	ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
		НСтр("ru = 'Неизвестный вид оповещения длительной операции ""%1"".'"), ВидОповещения);
	
	ВызватьИсключение ТекстОшибки;
	
КонецФункции

// Запись в очередь еще не записанных сообщений, которые были отправлены
// без использования процедуры ОбщегоНазначения.СообщитьПользователю.
//
// Параметры:
//  ИдентификаторЗадания - УникальныйИдентификатор
//
Процедура ЗаписатьОстаткиСообщенийПользователю(ИдентификаторЗадания)
	
	ПоследнийИдентификатор = ПоследнийИдентификатор(ИдентификаторЗадания);
	ФоновоеЗадание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ПоследнийИдентификатор);
	
	Если ФоновоеЗадание <> Неопределено
	   И Не МонопольныйРежимВФоновомЗадании(ФоновоеЗадание) Тогда
		
		Сообщения = ФоновоеЗадание.ПолучитьСообщенияПользователю(Истина);
		Для Каждого Сообщение Из Сообщения Цикл
			// @skip-check query-in-loop - ветка с запросом вызывается только при первом вызове в сеансе
			ОтправитьОповещениеКлиенту("СообщениеПользователю", Сообщение, ФоновоеЗадание, ИдентификаторЗадания);
		КонецЦикла;
	КонецЕсли;
	
КонецПроцедуры

// Параметры:
//  ФоновоеЗадание - ФоновоеЗадание - задание, активность которого нужно учесть.
//
// Возвращаемое значение:
//  Булево - если Истина, значит в сеансе установлен монопольный режим и
//    хотя бы одно фоновое задание активно (то есть запись в сеансе недоступна).
//
Функция МонопольныйРежимВФоновомЗадании(ФоновоеЗадание = Неопределено)
	
	Если Не МонопольныйРежим() Тогда
		Возврат Ложь;
	КонецЕсли;
	
	Если ФоновоеЗадание <> Неопределено
	   И ФоновоеЗадание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
		Возврат Истина;
	КонецЕсли;
	
	Отбор = Новый Структура;
	Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);
	
	Возврат ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор).Количество() > 0;
	
КонецФункции

Функция ЕстьФоновыеЗаданияВФайловойИБ()
	
	ЗапущеноЗаданийВФайловойИБ = 0;
	Если ОбщегоНазначения.ИнформационнаяБазаФайловая() Тогда
		Отбор = Новый Структура;
		Отбор.Вставить("Состояние", СостояниеФоновогоЗадания.Активно);
		ЗапущеноЗаданийВФайловойИБ = ФоновыеЗадания.ПолучитьФоновыеЗадания(Отбор).Количество();
	КонецЕсли;
	Возврат ЗапущеноЗаданийВФайловойИБ > 0;

КонецФункции

Функция ВозможноВыполнитьВФоне(ИмяПроцедуры)
	
	ЧастиИмени = СтрРазделить(ИмяПроцедуры, ".");
	Если ЧастиИмени.Количество() = 0 Тогда
		Возврат Ложь;
	КонецЕсли;
	
	Возврат Истина;
	
КонецФункции

Функция ВыполнитьФоновоеЗадание(ПараметрыВыполнения, ИмяМетода, Параметры, Ключ, Наименование)
	
	Если ТекущийРежимЗапуска() = Неопределено
		И ОбщегоНазначения.ИнформационнаяБазаФайловая() Тогда
		
		Сеанс = ПолучитьТекущийСеансИнформационнойБазы();
		Если ПараметрыВыполнения.ОжидатьЗавершение = Неопределено И Сеанс.ИмяПриложения = "BackgroundJob" Тогда
			ВызватьИсключение НСтр("ru = 'В файловой информационной базе невозможно одновременно выполнять более одного фонового задания'");
		ИначеЕсли Сеанс.ИмяПриложения = "COMConnection" Тогда
			ВызватьИсключение НСтр("ru = 'В файловой информационной базе можно запустить фоновое задание только из клиентского приложения'");
		КонецЕсли;
		
	КонецЕсли;
	
	Если ПараметрыВыполнения.БезРасширений Тогда
		Возврат РасширенияКонфигурации.ВыполнитьФоновоеЗаданиеБезРасширений(ИмяМетода, Параметры, Ключ, Наименование);
	
	ИначеЕсли ПараметрыВыполнения.СРасширениямиБазыДанных Тогда
		Возврат РасширенияКонфигурации.ВыполнитьФоновоеЗаданиеСРасширениямиБазыДанных(ИмяМетода, Параметры, Ключ, Наименование);
	Иначе
		Возврат ФоновыеЗадания.Выполнить(ИмяМетода, Параметры, Ключ, Наименование);
	КонецЕсли;
	
КонецФункции

Функция СписокПараметров(Знач Параметр1, Знач Параметр2, Знач Параметр3, Знач Параметр4,
	Знач Параметр5, Знач Параметр6, Знач Параметр7)
	
	ПереданныеПараметры = Новый Массив;
	ПереданныеПараметры.Добавить(Параметр7);
	ПереданныеПараметры.Добавить(Параметр6);
	ПереданныеПараметры.Добавить(Параметр5);
	ПереданныеПараметры.Добавить(Параметр4);
	ПереданныеПараметры.Добавить(Параметр3);
	ПереданныеПараметры.Добавить(Параметр2);
	ПереданныеПараметры.Добавить(Параметр1);
	
	Результат = Новый Массив;
	
	Для Каждого Параметр Из ПереданныеПараметры Цикл
		Если Результат.Количество() = 0 И Параметр = Неопределено Тогда
			Продолжить;
		КонецЕсли;
		Результат.Вставить(0, Параметр);
	КонецЦикла;
	
	Возврат Результат;

КонецФункции

Функция ПодготовитьПараметрыВыполнения(ПереданныйПараметр, ДляФункции)
	
	Результат = ОбщиеПараметрыВыполненияВФоне();
	
	Если ЗначениеЗаполнено(ПереданныйПараметр) Тогда
		Если ТипЗнч(ПереданныйПараметр) = Тип("Структура") Тогда
			Результат = ПереданныйПараметр;
		ИначеЕсли ДляФункции Тогда
			Идентификатор = Неопределено;
			Если ТипЗнч(ПереданныйПараметр) = Тип("ФормаКлиентскогоПриложения") Тогда
				Идентификатор = ПереданныйПараметр.УникальныйИдентификатор;
			ИначеЕсли ТипЗнч(ПереданныйПараметр) = Тип("УникальныйИдентификатор") Тогда
				Идентификатор = ПереданныйПараметр;
			КонецЕсли;
			ДобавитьПараметрыВыполненияДляВозвратаРезультата(Результат, Идентификатор);
		КонецЕсли;
	КонецЕсли;
	
	Результат.Вставить("ЭтоФункция", ДляФункции);
	
	Возврат Результат;
	
КонецФункции

Функция ОбщиеПараметрыВыполненияВФоне()
	
	Результат = Новый Структура;
	Результат.Вставить("ОжидатьЗавершение", ?(ПолучитьСкоростьКлиентскогоСоединения() = СкоростьКлиентскогоСоединения.Низкая, 4, 0.8));
	Результат.Вставить("НаименованиеФоновогоЗадания", "");
	Результат.Вставить("КлючФоновогоЗадания", "");
	Результат.Вставить("ЗапуститьНеВФоне", Ложь);
	Результат.Вставить("ЗапуститьВФоне", Ложь);
	Результат.Вставить("БезРасширений", Ложь);
	Результат.Вставить("СРасширениямиБазыДанных", Ложь);
	Результат.Вставить("ПрерватьВыполнениеЕслиОшибка", Ложь);
	Результат.Вставить("ОжидатьЗавершения", -1); // Обратная совместимость
	Результат.Вставить("ВнешнийОтчетОбработка", Неопределено);
	
	Возврат Результат;
	
КонецФункции

Процедура ДобавитьПараметрыВыполненияДляВозвратаРезультата(Параметры, ИдентификаторФормы)
	
	Параметры.Вставить("ИдентификаторФормы", ИдентификаторФормы); 
	Параметры.Вставить("АдресРезультата", Неопределено);
	
КонецПроцедуры

// Многопоточные операции

Функция ИмяМетодаМногопоточногоПроцесса()
	Возврат "ДлительныеОперации.ВыполнитьМногопоточныйПроцесс";
КонецФункции

Функция ВыполнитьМногопоточныйПроцесс(ПараметрыОперации) Экспорт
	
	УдалитьНесуществующиеПотоки();
	
	ИдентификаторПроцесса = ПараметрыОперации.ИдентификаторПроцесса;
	ПрерватьВыполнениеЕслиОшибка = ПараметрыОперации.ПараметрыВыполнения.ПрерватьВыполнениеЕслиОшибка;
	
	ДинамическоеПолучениеПорций = ТипЗнч(ПараметрыОперации.ПараметрыМетода) = Тип("Структура");
	Процент = 0;
	
	Если ДинамическоеПолучениеПорций Тогда
		ИмяМетодаПолученияПорций = ПараметрыОперации.ПараметрыМетода.ИмяМетодаПолученияПорций;
		КонтекстПолученияИОбработкиПорций = ПараметрыОперации.ПараметрыМетода.Контекст;
		Если Не КонтекстПолученияИОбработкиПорций.Свойство("Процент") Тогда
			КонтекстПолученияИОбработкиПорций.Вставить("Процент", 0);
		КонецЕсли;
		КонтекстПолученияИОбработкиПорций.Вставить("Кэш", Неопределено);
	Иначе
		КоличествоПорций = ПараметрыОперации.ПараметрыМетода;
		КоличествоОбработанныхПорций = Неопределено;
	КонецЕсли;
	
	ЗавершитьДосрочно = Ложь;
	
	ЗаданиеПроцесса = ПолучитьТекущийСеансИнформационнойБазы().ПолучитьФоновоеЗадание();
	ИдентификаторЗаданияПроцесса = ?(ЗаданиеПроцесса = Неопределено,
		Неопределено, ЗаданиеПроцесса.УникальныйИдентификатор);
	
	Пока Истина Цикл
		
		Если ДинамическоеПолучениеПорций Тогда
			НовыеПорции = Новый Соответствие;
			ПараметрыПолученияПорций = Новый Массив;
			ПараметрыПолученияПорций.Добавить(НовыеПорции);
			ПараметрыПолученияПорций.Добавить(КонтекстПолученияИОбработкиПорций);
			ОбщегоНазначения.ВыполнитьМетодКонфигурации(ИмяМетодаПолученияПорций, ПараметрыПолученияПорций);
			АдресаНовыхРезультатов = Новый Соответствие;
			Для Каждого КлючИЗначение Из НовыеПорции Цикл
				АдресаНовыхРезультатов.Вставить(КлючИЗначение.Ключ,
					ПоместитьВоВременноеХранилище(Неопределено, Новый УникальныйИдентификатор));
			КонецЦикла;
			ПодготовитьМногопоточнуюОперациюКЗапуску(ПараметрыОперации.ИмяМетода,
				АдресаНовыхРезультатов, ИдентификаторПроцесса, НовыеПорции, ПараметрыОперации);
		КонецЕсли;
		
		// @skip-check query-in-loop - Получение актуальных данных о потоках с учетом работы других процессов.
		Потоки = ПотокиОжидающиеОбработки(ИдентификаторПроцесса);
		Если Потоки.Количество() = 0 Тогда
			Прервать;
		КонецЕсли;
		
		Если Не ДинамическоеПолучениеПорций
		   И КоличествоОбработанныхПорций = Неопределено Тогда
			КоличествоОбработанныхПорций = КоличествоПорций - Потоки.Количество();
		КонецЕсли;
		
		Для Каждого Поток Из Потоки Цикл
			
			Если Поток.Статус <> СтатусДлительнойОперации().Создано Тогда
				Если ПрерватьВыполнениеЕслиОшибка Тогда
					ЗавершитьДосрочно = Истина;
					Прервать;
				КонецЕсли; 
				// @skip-check query-in-loop - ветка с запросом вызывается только при первом вызове в сеансе
				ОтправитьСообщенияПотока(Поток.ИдентификаторЗадания);
			КонецЕсли;
			
			Результат = Неопределено;
			Пока Результат = Неопределено Цикл
				// @skip-check query-in-loop - зачитывание актуальных данных о потоках из ИБ.
				ВыполнитьВФоне = ОжидатьСвободныйПоток(ИдентификаторПроцесса, ПрерватьВыполнениеЕслиОшибка);
				Если ВыполнитьВФоне = Неопределено Тогда
					ЗавершитьДосрочно = Истина;
					Прервать;
				КонецЕсли;
				
				// @skip-check query-in-loop - зачитывание актуальных данных о потоках из ИБ.
				Результат = ВыполнитьПоток(Поток, ПараметрыОперации, ВыполнитьВФоне, ИдентификаторЗаданияПроцесса);
				
			КонецЦикла;
			
			Если ЗавершитьДосрочно Тогда
				Прервать;
			КонецЕсли;
				
			Если Результат.Статус = СтатусДлительнойОперации().Ошибка Тогда
			
				Если ПрерватьВыполнениеЕслиОшибка Тогда
					ЗавершитьДосрочно = Истина;
					Прервать;
				КонецЕсли;
				
			КонецЕсли;
			
			Если ДинамическоеПолучениеПорций Тогда
				Процент = КонтекстПолученияИОбработкиПорций.Процент;
			Иначе
				КоличествоОбработанныхПорций = КоличествоОбработанныхПорций + 1;
				Процент = Окр(КоличествоОбработанныхПорций * 100 / КоличествоПорций);
			КонецЕсли;
			Процент = ?(Процент < 100, Процент, 99);
			Если Процент > 0 Тогда
				КлючПотока = Поток.КлючПотока.Получить();
				СообщитьПрогресс(Процент, Строка(КлючПотока), "ПрогрессМногопоточногоПроцесса");
			КонецЕсли;
			
		КонецЦикла;
		
		Если ЗавершитьДосрочно Тогда
			Прервать;
		КонецЕсли;
		
		// @skip-check query-in-loop - На каждой итерации необходимо зачитывать актуальные данные из ИБ.
		ОжидатьЗавершениеВсехПотоков(ИдентификаторПроцесса, ПрерватьВыполнениеЕслиОшибка, ЗавершитьДосрочно);
		
		Если ЗавершитьДосрочно Тогда
			Прервать;
		КонецЕсли;
		
	КонецЦикла;
	
	Если ЗавершитьДосрочно Тогда
		ОтменитьВыполнениеВсехПотоков(ИдентификаторПроцесса);
	КонецЕсли;
	
	Если Процент > 0 Тогда
		СообщитьПрогресс(100, "", "ПрогрессМногопоточногоПроцесса");
	КонецЕсли;
	// запрос к РС и получение всех результатов 
	
	ПотокиПроцесса = ПотокиДлительныхОпераций(ИдентификаторПроцесса); 
	РезультатДлительнойОперации = НовыйРезультатДлительнойОперации();
	Результаты = Новый Соответствие();
	
	Для Каждого Поток Из ПотокиПроцесса Цикл
		Если Поток.Статус = СтатусДлительнойОперации().Создано Тогда
			Продолжить;
		КонецЕсли;
		
		Ключ = Поток.КлючПотока.Получить();
		Результаты.Вставить(Ключ, Новый Структура(Новый ФиксированнаяСтруктура(РезультатДлительнойОперации)));
		Результаты[Ключ].АдресРезультата = ПоместитьВоВременноеХранилище(Поток.Результат.Получить(), Поток.АдресРезультата);
		
		ЗаполнитьЗначенияСвойств(Результаты[Ключ], Поток, 
			"Статус, ПодробноеПредставлениеОшибки, КраткоеПредставлениеОшибки, ИдентификаторЗадания");
		
		// @skip-check query-in-loop - ветка с запросом вызывается только при первом вызове в сеансе
		ОтправитьСообщенияПотока(Поток.ИдентификаторЗадания);
	КонецЦикла;
	
	Возврат Результаты;
	
КонецФункции

// Выполнить указанный поток
//
// Параметры:
//  Поток - РегистрСведенийНаборЗаписей.ДлительныеОперации
//  ПараметрыОперации - см. ПараметрыМногопоточнойОперации
//  ВыполнитьВФоне - Булево
// 
// Возвращаемое значение:
//   см. ВыполнитьВФоне
//
Функция ВыполнитьПоток(Поток, ПараметрыОперации, ВыполнитьВФоне, ИдентификаторЗаданияПроцесса)
	
	ПараметрыВыполнения = ПараметрыВыполненияВФоне();
	ПараметрыВыполнения.НаименованиеФоновогоЗадания = Поток.Наименование;
	ПараметрыВыполнения.ОжидатьЗавершение = 0;
	ПараметрыВыполнения.ЗапуститьНеВФоне = ПараметрыОперации.ПараметрыВыполнения.ЗапуститьНеВФоне
		Или МонопольныйРежим() Или Не ВыполнитьВФоне;
	
	СвойстваПотока = Новый Структура;
	СвойстваПотока.Вставить("ИдентификаторПроцесса", Поток.ИдентификаторПроцесса);
	СвойстваПотока.Вставить("ИдентификаторПотока",   Поток.ИдентификаторПотока);
	ПараметрыВыполнения.Вставить("СвойстваИсполняющегоПотокаМногопоточнойДлительнойОперации", СвойстваПотока);
	
	Если ТипЗнч(Поток.ПараметрыПотока) = Тип("ХранилищеЗначения") Тогда
		ПараметрыМетода = Поток.ПараметрыПотока.Получить();
	Иначе
		ПараметрыМетода = Новый Массив;
	КонецЕсли;
	
	ПараметрыВыполнения = ПодготовитьПараметрыВыполнения(ПараметрыВыполнения, ПараметрыОперации.ДляФункции);
	ПараметрыВыполнения.ВнешнийОтчетОбработка = ПараметрыОперации.ПараметрыВыполнения.ВнешнийОтчетОбработка;
	
	ПараметрыВыполнения.АдресРезультата = Поток.АдресРезультата;
	
	РезультатЗапуска = Неопределено;
	МаксимумПотоков  = ДопустимоеКоличествоПотоков();
	
	Блокировка = Новый БлокировкаДанных;
	Блокировка.Добавить("Константа.КоличествоПотоковДлительныхОпераций");
	УстановитьПолноеИмяПрикладнойПроцедуры(ПараметрыОперации.ИмяМетода);
	
	ПотокЗанят = Ложь;
	
	Если Не ПараметрыВыполнения.ЗапуститьНеВФоне Тогда
	
		НачатьТранзакцию();
		Попытка
			Блокировка.Заблокировать();
			
			Потоки = АктивныеПотоки();
			Если Потоки.Количество() < МаксимумПотоков Тогда
				ВременныйРезультатЗапуска = НовыйРезультатДлительнойОперации();
				ВременныйРезультатЗапуска.ИдентификаторЗадания = ИдентификаторЗаданияПроцесса;
				ВременныйРезультатЗапуска.Статус = СтатусДлительнойОперации().Выполняется;
				ОбновитьСведенияОбПотоке(Поток, ВременныйРезультатЗапуска, Ложь);
				ПотокЗанят = Истина;
			КонецЕсли;
		
			ЗафиксироватьТранзакцию();
		Исключение
			ОтменитьТранзакцию();
			ВызватьИсключение;
		КонецПопытки;
		
	КонецЕсли;
	
	Попытка
	
		Если ПараметрыВыполнения.ЗапуститьНеВФоне Или ПотокЗанят Тогда
			РезультатЗапуска = ВыполнитьВФоне(ПараметрыОперации.ИмяМетода, ПараметрыМетода, ПараметрыВыполнения);
			УстановитьПолноеИмяПрикладнойПроцедуры(ИмяМетодаМногопоточногоПроцесса());
			ОбновитьСведенияОбПотоке(Поток, РезультатЗапуска);
		КонецЕсли;
	
	Исключение
		
		Если ПотокЗанят Тогда
			ПустойРезультатЗапуска = НовыйРезультатДлительнойОперации();
			ОбновитьСведенияОбПотоке(Поток, ПустойРезультатЗапуска, Ложь);
		КонецЕсли;
		ВызватьИсключение;
		
	КонецПопытки;
	
	Возврат РезультатЗапуска;
	
КонецФункции

Функция СтатусИзСостояния(Состояние)
	
	Если Состояние = СостояниеФоновогоЗадания.Завершено Тогда
		Возврат СтатусДлительнойОперации().Выполнено;
	ИначеЕсли Состояние = СостояниеФоновогоЗадания.Отменено Тогда
		Возврат СтатусДлительнойОперации().Отменено;
	ИначеЕсли Состояние = СостояниеФоновогоЗадания.Активно Тогда
		Возврат СтатусДлительнойОперации().Выполняется;
	КонецЕсли;
	
	Возврат СтатусДлительнойОперации().Ошибка;

КонецФункции

Процедура ОбновитьСведенияОбПотоке(Поток, РезультатЗапуска = Неопределено, НоваяПопытка = Истина)
	
	РезультатЗапускаУказан = РезультатЗапуска <> Неопределено;
	
	Если РезультатЗапуска = Неопределено Тогда
		РезультатЗапуска = НовыйРезультатДлительнойОперации(); 
		
		Если ЗначениеЗаполнено(Поток.ИдентификаторЗадания) Тогда
			ПоследнийИдентификатор = ПоследнийИдентификаторЗаданияУправляющегоПотока(Поток);
			Задание = НайтиЗаданиеПоИдентификатору(ПоследнийИдентификатор);
			
			Если Задание <> Неопределено Тогда
				РезультатЗапуска.Статус = СтатусИзСостояния(Задание.Состояние);
				
				Если Задание.ИнформацияОбОшибке <> Неопределено Тогда
					УстановитьСвойстваОшибки(РезультатЗапуска, Задание.ИнформацияОбОшибке);
				КонецЕсли;
			Иначе
				РезультатЗапуска.Статус = СтатусДлительнойОперации().Ошибка;
			КонецЕсли;
		Иначе
			РезультатЗапуска.Статус = СтатусДлительнойОперации().Создано;
		КонецЕсли;
	КонецЕсли;
	
	ИдентификаторПотока   = Поток.ИдентификаторПотока;
	ИдентификаторПроцесса = Поток.ИдентификаторПроцесса;
	
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ДлительныеОперации"); 
	ЭлементБлокировки.УстановитьЗначение("ИдентификаторПроцесса", ИдентификаторПроцесса);
	ЭлементБлокировки.УстановитьЗначение("ИдентификаторПотока",   ИдентификаторПотока);
	
	НачатьТранзакцию();
	Попытка
		Блокировка.Заблокировать();
		
		УстановитьПривилегированныйРежим(Истина);
		
		НаборЗаписей = РегистрыСведений.ДлительныеОперации.СоздатьНаборЗаписей();
		НаборЗаписей.Отбор.ИдентификаторПроцесса.Установить(ИдентификаторПроцесса);
		НаборЗаписей.Отбор.ИдентификаторПотока.Установить(ИдентификаторПотока);
		
		НаборЗаписей.Прочитать();
		
		Если НаборЗаписей.Количество() > 0 Тогда
			Запись = НаборЗаписей.Получить(0);
			
			Если РезультатЗапускаУказан Тогда
				Запись.ИдентификаторЗадания = РезультатЗапуска.ИдентификаторЗадания;
				Запись.КлючПотока   = Поток.КлючПотока;
				Если НоваяПопытка Тогда
					Запись.НомерПопытки = Поток.НомерПопытки + 1;
				КонецЕсли;
			КонецЕсли;
			Запись.Статус = РезультатЗапуска.Статус;
			Если РезультатЗапуска.Статус = СтатусДлительнойОперации().Ошибка Тогда
				Запись.ПодробноеПредставлениеОшибки = РезультатЗапуска.ПодробноеПредставлениеОшибки;
				Запись.КраткоеПредставлениеОшибки   = РезультатЗапуска.КраткоеПредставлениеОшибки;
			КонецЕсли;
			
			ЗаполнитьЗначенияСвойств(Поток, Запись);
			
			НаборЗаписей.Записать();
		КонецЕсли;
		
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;
	
КонецПроцедуры

Функция ПервыйИдентификаторЗаданияУправляющегоПотока(ИдентификаторПроцесса)
	
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	ДлительныеОперации.ИдентификаторЗадания КАК ИдентификаторЗадания
	|ИЗ
	|	РегистрСведений.ДлительныеОперации КАК ДлительныеОперации
	|ГДЕ
	|	ДлительныеОперации.ИдентификаторПроцесса = &ИдентификаторПроцесса
	|	И ДлительныеОперации.ИдентификаторПотока = &ПустойУникальныйИдентификатор";
	
	Запрос.УстановитьПараметр("ИдентификаторПроцесса", ИдентификаторПроцесса);
	Запрос.УстановитьПараметр("ПустойУникальныйИдентификатор",
		ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор());
	
	Выборка = Запрос.Выполнить().Выбрать();
	Если Выборка.Следующий() Тогда
		Возврат Выборка.ИдентификаторЗадания;
	КонецЕсли;
	
	ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
		НСтр("ru = 'Не удалось найти запись управляющего потока многопоточной длительной операции %1'"),
		Строка(ИдентификаторПроцесса));
	
	ВызватьИсключение ТекстОшибки;
	
КонецФункции

Функция ПоследнийИдентификаторЗаданияУправляющегоПотока(Поток)
	
	ПоследнийИдентификатор = Поток.ИдентификаторЗадания;
	
	Если ЗначениеЗаполнено(Поток.ИдентификаторПотока) Тогда
		Возврат ПоследнийИдентификатор;
	КонецЕсли;
	
	ИдентификаторЗадания = Поток.КлючПотока.Получить();
	
	Если ТипЗнч(ИдентификаторЗадания) = Тип("УникальныйИдентификатор") Тогда
		ПоследнийИдентификатор = ИдентификаторЗадания;
	КонецЕсли;
	
	Возврат ПоследнийИдентификатор;
	
КонецФункции

// Параметры:
//  СвойстваПотока - Структура:
//   * ИдентификаторПроцесса - УникальныйИдентификатор
//   * ИдентификаторПотока - УникальныйИдентификатор
//
//  Результат - Произвольный - результат работы функции исполняющего потока.
//
Процедура УстановитьРезультатПотока(СвойстваПотока, Результат)
	
	Результат = Новый ХранилищеЗначения(Результат);
	
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ДлительныеОперации"); 
	ЭлементБлокировки.УстановитьЗначение("ИдентификаторПроцесса", СвойстваПотока.ИдентификаторПроцесса);
	ЭлементБлокировки.УстановитьЗначение("ИдентификаторПотока",   СвойстваПотока.ИдентификаторПотока);
	
	НачатьТранзакцию();
	Попытка
		Блокировка.Заблокировать();
		
		УстановитьПривилегированныйРежим(Истина);
		
		НаборЗаписей = РегистрыСведений.ДлительныеОперации.СоздатьНаборЗаписей();
		НаборЗаписей.Отбор.ИдентификаторПроцесса.Установить(СвойстваПотока.ИдентификаторПроцесса);
		НаборЗаписей.Отбор.ИдентификаторПотока.Установить(СвойстваПотока.ИдентификаторПотока);
		
		НаборЗаписей.Прочитать();
		
		Если НаборЗаписей.Количество() > 0 Тогда
			Запись = НаборЗаписей.Получить(0);
			Запись.Результат = Результат;
			НаборЗаписей.Записать();
		КонецЕсли;
		
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;
	
КонецПроцедуры

// Ожидать, пока количество выполняющихся потоков не станет меньше максимального.
//
// Параметры:
//  ДопустимоеКоличествоПотоков - Число
//  ИдентификаторПроцесса - УникальныйИдентификатор
//  ЗавершитьДосрочноЕслиОшибка - Булево
//  ВыполнитьНеВФоне - Булево - возвращаемое значение
//
// Возвращаемое значение:
//  Булево
//
Функция ОжидатьСвободныйПоток(ИдентификаторПроцесса, ЗавершитьДосрочноЕслиОшибка)
	
	МаксимумПотоков = ДопустимоеКоличествоПотоков();
	ВыполнитьВФоне = МаксимумПотоков > 1;
	
	Блокировка = Новый БлокировкаДанных;
	Блокировка.Добавить("Константа.КоличествоПотоковДлительныхОпераций");
	
	Пока Истина Цикл
		
		НачатьТранзакцию();
		Попытка
			Блокировка.Заблокировать();
			Потоки = АктивныеПотоки();
			ЗафиксироватьТранзакцию();
		Исключение
			ОтменитьТранзакцию();
			ВызватьИсключение;
		КонецПопытки;
		
		Если Потоки.Количество() < МаксимумПотоков Тогда
			Прервать;
		КонецЕсли;
		
		ЕстьЗавершенныеПотоки = ЕстьЗавершенныеПотоки(Потоки,
			ЗавершитьДосрочноЕслиОшибка, ИдентификаторПроцесса);
		
		Если ЗавершитьДосрочноЕслиОшибка И ЕстьЗавершенныеПотоки = Неопределено Тогда 
			ВыполнитьВФоне = Неопределено; // Ошибка в потоке.
			Прервать;
		КонецЕсли;
		
		Если ЕстьЗавершенныеПотоки Или Не ВыполнитьВФоне Тогда
			Прервать;
		КонецЕсли;
		
		Отбор = Новый Структура;
		Отбор.Вставить("ИдентификаторПотока", ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор());
		УправляющиеПотоки = Потоки.НайтиСтроки(Отбор);
		Для Каждого УправляющийПоток Из УправляющиеПотоки Цикл
			Потоки.Удалить(УправляющийПоток);
		КонецЦикла;
		
		Если Потоки.Найти(ИдентификаторПроцесса, "ИдентификаторПроцесса") = Неопределено Тогда
			// Не ждем, когда у процесса нет ни одного исполняющего потока.
			ВыполнитьВФоне = Ложь;
			Прервать;
		КонецЕсли;
		
		Если ОжидатьЗавершениеПотока(Потоки[0]) Тогда // Задание выполнено.
			ОбновитьСведенияОбПотоке(Потоки[0]);
		КонецЕсли;
		
	КонецЦикла;
	
	Возврат ВыполнитьВФоне;
	
КонецФункции

// Отправляет сообщения потока, которые были выведены
// без использования процедуры ОбщегоНазначения.СообщитьПользователю.
//
// Параметры:
//  ИдентификаторЗадания - УникальныйИдентификатор
//
Процедура ОтправитьСообщенияПотока(Знач ИдентификаторЗадания)
	
	Если Не ЗначениеЗаполнено(ИдентификаторЗадания) Тогда
		Возврат;
	КонецЕсли;
	
	ФоновоеЗадание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(ИдентификаторЗадания);
	Если ФоновоеЗадание = Неопределено Тогда
		Возврат;
	КонецЕсли;
	
	Сообщения = ФоновоеЗадание.ПолучитьСообщенияПользователю(Истина);
	Для Каждого Сообщение Из Сообщения Цикл
		// @skip-check query-in-loop - ветка с запросом вызывается только при первом вызове в сеансе
		ОтправитьОповещениеКлиенту("СообщениеПользователю", Сообщение);
	КонецЦикла;

КонецПроцедуры

// Ожидать завершение всех потоков.
//
// Параметры:
//  Группы - Соответствие
//
Процедура ОжидатьЗавершениеВсехПотоков(ИдентификаторПроцесса, ЗавершитьДосрочноЕслиОшибка, ЗавершитьДосрочно)
	
	Потоки = АктивныеПотоки(ИдентификаторПроцесса);
	
	Пока Потоки.Количество() > 0 Цикл
		ЕстьЗавершенныеПотоки = ЕстьЗавершенныеПотоки(Потоки,
			ЗавершитьДосрочноЕслиОшибка, ИдентификаторПроцесса);
		
		Если ЕстьЗавершенныеПотоки = Неопределено Тогда
			ЗавершитьДосрочно = Истина;
			Прервать;
		КонецЕсли;
		
		Если Не ЕстьЗавершенныеПотоки Тогда
			ОжидатьЗавершениеПотока(Потоки[0]);
		КонецЕсли;
	КонецЦикла;
	
КонецПроцедуры

// Ожидать завершение потока в течение указанной длительности.
//
// Параметры:
//   Поток - СтрокаТаблицыЗначений - поток, завершение которого необходимо ожидать.
//   Длительность - Число - максимальная длительность ожидания в секундах.
//
// Возвращаемое значение:
//  Булево - Истина - поток выполнен, Ложь - поток еще выполняется.
//
Функция ОжидатьЗавершениеПотока(Поток, Длительность = 1)
	
	Если ЗначениеЗаполнено(Поток.ИдентификаторЗадания) Тогда
		
		Задание = ФоновыеЗадания.НайтиПоУникальномуИдентификатору(Поток.ИдентификаторЗадания);
		
		Если Задание <> Неопределено Тогда
			Задание = Задание.ОжидатьЗавершенияВыполнения(Длительность);
			ЗаданиеЗавершено = (Задание.Состояние <> СостояниеФоновогоЗадания.Активно);
			Возврат ЗаданиеЗавершено;
		КонецЕсли;
		
	КонецЕсли;
	
	Возврат Истина;
	
КонецФункции

Функция ЕстьЗавершенныеПотоки(Потоки, ЗавершитьДосрочноЕслиОшибка, ИдентификаторПроцесса)
	
	ЕстьЗавершенныеПотоки = Ложь;
	Индекс = Потоки.Количество() - 1;
	
	Пока Индекс >= 0 Цикл
		Поток = Потоки[Индекс];
		Индекс = Индекс - 1;
		
		Если Не ЗначениеЗаполнено(Поток.ИдентификаторЗадания) Тогда
			Продолжить;
		КонецЕсли;
		
		ПоследнийИдентификатор = ПоследнийИдентификаторЗаданияУправляющегоПотока(Поток);
		Результат = ЗаданиеВыполнено(ПоследнийИдентификатор, Истина);
		
		Если Результат.Статус = "Выполняется" Тогда
			Продолжить;
		КонецЕсли;
		
		ОбновитьСведенияОбПотоке(Поток);
		ЭтоПотокТекущегоПроцесса = (Поток.ИдентификаторПроцесса = ИдентификаторПроцесса);
		Потоки.Удалить(Поток);
		ЕстьЗавершенныеПотоки = Истина;
		
		Если Результат.Статус = "Выполнено" Тогда
			Продолжить;
		КонецЕсли;
		
		Если Результат.Статус = "Ошибка" Тогда
			ЗаписатьОшибку(Результат.ТекстОшибки);
		КонецЕсли;
		
		Если ЗавершитьДосрочноЕслиОшибка = Истина
		   И ЭтоПотокТекущегоПроцесса Тогда
			Возврат Неопределено;
		КонецЕсли;
		
	КонецЦикла;
	
	Возврат ЕстьЗавершенныеПотоки;
	
КонецФункции

// Отменить выполнение потоков, если они активны.
// 
// Параметры:
//  ИдентификаторПроцесса - УникальныйИдентификатор
// 
Процедура ОтменитьВыполнениеВсехПотоков(ИдентификаторПроцесса)
	
	УстановитьПривилегированныйРежим(Истина);
	
	Потоки = АктивныеПотоки(ИдентификаторПроцесса);
	Для Каждого Поток Из Потоки Цикл
		Если ЗначениеЗаполнено(Поток.ИдентификаторЗадания) Тогда
			ОтменитьВыполнениеЗадания(Поток.ИдентификаторЗадания);
		КонецЕсли;
	КонецЦикла;
	
	ОжидатьЗавершениеВсехПотоков(ИдентификаторПроцесса, Ложь, Ложь)
	
КонецПроцедуры

// Удалить данные о потоках, если они активны.
// 
Процедура УдалитьНесуществующиеПотоки()
	
	УстановитьПривилегированныйРежим(Истина);
	
	ВсеПотоки = ПотокиДлительныхОпераций();
	
	Процессы = ВсеПотоки.Скопировать(, "ИдентификаторПроцесса");
	Процессы.Свернуть("ИдентификаторПроцесса");
	ИдентификаторыПроцессов = Процессы.ВыгрузитьКолонку("ИдентификаторПроцесса");
	
	ОтборУправляющегоПотока = Новый Структура;
	ОтборУправляющегоПотока.Вставить("ИдентификаторПроцесса");
	ОтборУправляющегоПотока.Вставить("ИдентификаторПотока",
		ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор());
	
	ТекущаяДатаСеанса = ТекущаяДатаСеанса();
	СрокОтмены = 24*60*60; // 24 часа.
	СрокУстаревания = 15*60; // 15 минут.
	
	Для Каждого ИдентификаторПроцесса Из ИдентификаторыПроцессов Цикл
		ОтборУправляющегоПотока.ИдентификаторПроцесса = ИдентификаторПроцесса;
		НайденныеСтроки = ВсеПотоки.НайтиСтроки(ОтборУправляющегоПотока);
		УправляющийПоток = ?(НайденныеСтроки.Количество() > 0, НайденныеСтроки[0], Неопределено);
		
		Если УправляющийПоток <> Неопределено
		   И УправляющийПоток.ДатаСоздания + СрокОтмены > ТекущаяДатаСеанса Тогда
			
			Если Не ЗначениеЗаполнено(УправляющийПоток.ИдентификаторЗадания)
			   И УправляющийПоток.ДатаСоздания + СрокУстаревания > ТекущаяДатаСеанса Тогда
				Продолжить;
			КонецЕсли;
			ИдентификаторЗадания = ПоследнийИдентификаторЗаданияУправляющегоПотока(УправляющийПоток);
			Задание = НайтиЗаданиеПоИдентификатору(ИдентификаторЗадания);
			Если Задание <> Неопределено
			   И (Задание.Состояние = СостояниеФоновогоЗадания.Активно
			      Или Задание.Состояние = СостояниеФоновогоЗадания.ЗавершеноАварийно
			        И УправляющийПоток.НомерПопытки < КоличествоПопыток()
			        И УправляющийПоток.ДатаСоздания + СрокУстаревания > ТекущаяДатаСеанса) Тогда
				Продолжить;
			КонецЕсли;
		КонецЕсли;
		
		Отбор = Новый Структура("ИдентификаторПроцесса", ИдентификаторПроцесса);
		ПотокиПроцесса = ВсеПотоки.Скопировать(Отбор);
		ПотокиПроцесса.Сортировать("ИдентификаторПотока");
		Для Каждого ПотокПроцесса Из ПотокиПроцесса Цикл
			ИдентификаторЗадания = ПоследнийИдентификаторЗаданияУправляющегоПотока(ПотокПроцесса);
			Задание = НайтиЗаданиеПоИдентификатору(ИдентификаторЗадания);
			Если Задание <> Неопределено
			   И Задание.Состояние = СостояниеФоновогоЗадания.Активно Тогда
				ОтменитьВыполнениеЗадания(ПотокПроцесса.ИдентификаторЗадания);
			КонецЕсли;
		КонецЦикла;
		УдалитьДанныеОПотоках(ИдентификаторПроцесса);
		
	КонецЦикла;
	
КонецПроцедуры

// Описание потоков.
//
// Параметры:
//  ИдентификаторПроцесса - УникальныйИдентификатор
//                        - Неопределено - вернуть все
//
// Возвращаемое значение:
//    ТаблицаЗначений:
//      * Наименование - Строка - произвольное наименование потока (используется в наименовании фонового задания).
//      * ИдентификаторЗадания - УникальныйИдентификатор - уникальный идентификатор фонового задания.
//      * ИдентификаторПроцесса - УникальныйИдентификатор - уникальный идентификатор процесса. 
//      * ИдентификаторПотока - УникальныйИдентификатор - уникальный идентификатор потока внутри процесса.
//      * ПараметрыМетода - Произвольный - параметры для процедуры или функции.
//      * АдресРезультата - Строка - адрес временного хранилища для сохранения результата выполнения фонового задания.
//      * ИмяМетода - Строка - имя метода для обработки порции
//      * КлючПотока - Произвольный - уникальный идентификатор порции (набора) данных.
//      * Результат - ХранилищеЗначения
//      * НомерПопытки - Число
//      * ПодробноеПредставлениеОшибки - Строка
//      * КраткоеПредставлениеОшибки - Строка
//      * ДатаСоздания - Дата
//      * Статус - Строка
//
Функция ПотокиДлительныхОпераций(ИдентификаторПроцесса = Неопределено)
	
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	ДлительныеОперации.АдресРезультата КАК АдресРезультата,
	|	ДлительныеОперации.Наименование КАК Наименование,
	|	ДлительныеОперации.ИдентификаторЗадания КАК ИдентификаторЗадания,
	|	ДлительныеОперации.ИдентификаторПроцесса КАК ИдентификаторПроцесса,
	|	ДлительныеОперации.ИдентификаторПотока КАК ИдентификаторПотока,
	|	ДлительныеОперации.КлючПотока КАК КлючПотока,
	|	ДлительныеОперации.ИмяМетода КАК ИмяМетода,
	|	ДлительныеОперации.Результат КАК Результат,
	|	ДлительныеОперации.НомерПопытки КАК НомерПопытки,
	|	ДлительныеОперации.ПодробноеПредставлениеОшибки КАК ПодробноеПредставлениеОшибки,
	|	ДлительныеОперации.КраткоеПредставлениеОшибки КАК КраткоеПредставлениеОшибки,
	|	ДлительныеОперации.ДатаСоздания КАК ДатаСоздания,
	|	ДлительныеОперации.Статус КАК Статус
	|ИЗ
	|	РегистрСведений.ДлительныеОперации КАК ДлительныеОперации";
	
	Если ЗначениеЗаполнено(ИдентификаторПроцесса) Тогда
		
		Запрос.Текст = Запрос.Текст + "
		|ГДЕ
		|	ДлительныеОперации.ИдентификаторПроцесса = &ИдентификаторПроцесса
		|	И ДлительныеОперации.ИдентификаторПотока <> &ПустойУникальныйИдентификатор";
		
		Запрос.УстановитьПараметр("ИдентификаторПроцесса", ИдентификаторПроцесса);
		Запрос.УстановитьПараметр("ПустойУникальныйИдентификатор",
			ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор());
		
	КонецЕсли;
	
	УстановитьПривилегированныйРежим(Истина);
	
	Возврат Запрос.Выполнить().Выгрузить();
	
КонецФункции

// Возвращаемое значение:
//  Структура: 
//   * Статус               - Строка - "Выполняется", если задание еще не завершилось;
//                                     "Выполнено", если задание было успешно выполнено;
//                                     "Ошибка", если задание завершено с ошибкой;
//                                     "Отменено", если задание отменено пользователем или администратором;
//                                      Пустая строка, если задание не запускалось.
//   * ИдентификаторЗадания - УникальныйИдентификатор - если Статус = "Выполняется", то содержит 
//                                     идентификатор запущенного фонового задания.
//                          - Неопределено - если Статус <> "Выполняется" и фоновое задание не запускалось.
//   * АдресРезультата       - Строка - адрес временного хранилища, в которое будет помещено Соответствие:
//                                      ** Ключ - Произвольный 
//                                      ** Значение - Структура
//   * КраткоеПредставлениеОшибки   - Строка - краткая информация об исключении, если Статус = "Ошибка".
//   * ПодробноеПредставлениеОшибки - Строка - подробная информация об исключении, если Статус = "Ошибка".
//   * Сообщения - ФиксированныйМассив - если Статус <> "Выполняется", то массив объектов СообщениеПользователю,
//                                      которые были сформированы в фоновом задании.
//
Функция НовыйРезультатДлительнойОперации()
	
	Результат = Новый Структура;
	Результат.Вставить("Статус",                       "");
	Результат.Вставить("ИдентификаторЗадания",         Неопределено);
	Результат.Вставить("АдресРезультата",              "");
	Результат.Вставить("КраткоеПредставлениеОшибки",   "");
	Результат.Вставить("ПодробноеПредставлениеОшибки", "");
	Результат.Вставить("Сообщения", Новый ФиксированныйМассив(Новый Массив));
	
	Возврат Результат;
	
КонецФункции

Функция АктивныеПотоки(ИдентификаторПроцесса = Неопределено)
	
	УстановитьПривилегированныйРежим(Истина);
	
	Запрос = Новый Запрос;
	ТекстЗапроса =
	"ВЫБРАТЬ
	|	ДлительныеОперации.ИдентификаторПотока КАК ИдентификаторПотока,
	|	ДлительныеОперации.АдресРезультата КАК АдресРезультата,
	|	ДлительныеОперации.ИдентификаторЗадания КАК ИдентификаторЗадания,
	|	ДлительныеОперации.ИдентификаторПроцесса КАК ИдентификаторПроцесса,
	|	ДлительныеОперации.ИмяМетода КАК ИмяМетода,
	|	ДлительныеОперации.ИмяПользователя КАК ИмяПользователя,
	|	ДлительныеОперации.КлючПотока КАК КлючПотока,
	|	ДлительныеОперации.Наименование КАК Наименование,
	|	ДлительныеОперации.НомерПопытки КАК НомерПопытки,
	|	ДлительныеОперации.Статус КАК Статус
	|ИЗ
	|	РегистрСведений.ДлительныеОперации КАК ДлительныеОперации
	|ГДЕ
	|	ДлительныеОперации.Статус = &Выполняется";
		
	Если ЗначениеЗаполнено(ИдентификаторПроцесса) Тогда
		ТекстЗапроса = ТекстЗапроса + "
		|	И ДлительныеОперации.ИдентификаторПроцесса = &ИдентификаторПроцесса
		|	И ДлительныеОперации.ИдентификаторПотока <> &ПустойУникальныйИдентификатор";
		
		Запрос.УстановитьПараметр("ИдентификаторПроцесса", ИдентификаторПроцесса);
		Запрос.УстановитьПараметр("ПустойУникальныйИдентификатор",
			ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор());
	КонецЕсли;
	
	Запрос.Текст = ТекстЗапроса;
	Запрос.УстановитьПараметр("Выполняется", СтатусДлительнойОперации().Выполняется);
	
	Возврат Запрос.Выполнить().Выгрузить()
	
КонецФункции

// Параметры:
//   ИдентификаторПроцесса - УникальныйИдентификатор
//
// Возвращаемое значение:
//  ТаблицаЗначений:
//   * ИдентификаторПотока   - УникальныйИдентификатор
//   * АдресРезультата       - Строка
//   * ИдентификаторЗадания  - УникальныйИдентификатор
//   * ИдентификаторПроцесса - УникальныйИдентификатор
//   * ИмяМетода             - Строка
//   * КлючПотока            - ХранилищеЗначения
//   * Наименование          - Строка
//   * НомерПопытки          - Число
//   * ПараметрыПотока       - ХранилищеЗначения
//   * Статус                - Строка
//  
Функция ПотокиОжидающиеОбработки(ИдентификаторПроцесса)
	
	УстановитьПривилегированныйРежим(Истина);
	
	Запрос = Новый Запрос;
	Запрос.Текст =
	"ВЫБРАТЬ
	|	ДлительныеОперации.ИдентификаторПотока КАК ИдентификаторПотока,
	|	ДлительныеОперации.АдресРезультата КАК АдресРезультата,
	|	ДлительныеОперации.ИдентификаторЗадания КАК ИдентификаторЗадания,
	|	ДлительныеОперации.ИдентификаторПроцесса КАК ИдентификаторПроцесса,
	|	ДлительныеОперации.ИмяМетода КАК ИмяМетода,
	|	ДлительныеОперации.ИмяПользователя КАК ИмяПользователя,
	|	ДлительныеОперации.КлючПотока КАК КлючПотока,
	|	ДлительныеОперации.Наименование КАК Наименование,
	|	ДлительныеОперации.НомерПопытки КАК НомерПопытки,
	|	ДлительныеОперации.ПараметрыПотока КАК ПараметрыПотока,
	|	ДлительныеОперации.Статус КАК Статус
	|ИЗ
	|	РегистрСведений.ДлительныеОперации КАК ДлительныеОперации
	|ГДЕ
	|	ДлительныеОперации.ИдентификаторПроцесса = &ИдентификаторПроцесса
	|	И ДлительныеОперации.ИдентификаторПотока <> &ПустойУникальныйИдентификатор
	|	И (ДлительныеОперации.Статус = &Создан
	|			ИЛИ ДлительныеОперации.Статус = &Ошибка
	|				И ДлительныеОперации.НомерПопытки < &КоличествоПопыток)";
	
	Запрос.УстановитьПараметр("ИдентификаторПроцесса", ИдентификаторПроцесса);
	Запрос.УстановитьПараметр("КоличествоПопыток",     КоличествоПопыток());
	Запрос.УстановитьПараметр("Создан", СтатусДлительнойОперации().Создано);
	Запрос.УстановитьПараметр("Ошибка", СтатусДлительнойОперации().Ошибка);
	Запрос.УстановитьПараметр("ПустойУникальныйИдентификатор",
		ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор());
	
	Возврат Запрос.Выполнить().Выгрузить();
	
КонецФункции

Процедура ПодготовитьМногопоточнуюОперациюКЗапуску(Знач ИмяМетода, АдресРезультатов,
			Знач ИдентификаторПроцесса, Знач Порции, ОбновленныеПараметрыОперации = Неопределено)
	
	ИмяПользователя =  "";
	Если Не Пользователи.ЭтоПолноправныйПользователь() Тогда
		ИмяПользователя = ПользователиИнформационнойБазы.ТекущийПользователь().Имя;
	КонецЕсли;
	ТекущаяДатаСеанса = ТекущаяДатаСеанса();
	
	УстановитьПривилегированныйРежим(Истина);
	
	НаборЗаписей = РегистрыСведений.ДлительныеОперации.СоздатьНаборЗаписей();
	НаборЗаписей.Отбор.ИдентификаторПроцесса.Установить(ИдентификаторПроцесса);
	
	Для каждого КлючЗначение Из Порции Цикл
		
		Запись = НаборЗаписей.Добавить();
		
		Запись.ИдентификаторПроцесса = ИдентификаторПроцесса;
		Запись.ИдентификаторПотока   = Новый УникальныйИдентификатор;
		Запись.ИмяМетода             = ИмяМетода;
		Запись.КлючПотока            = Новый ХранилищеЗначения(КлючЗначение.Ключ);
		Запись.АдресРезультата       = АдресРезультатов[КлючЗначение.Ключ];
		Запись.Статус                = СтатусДлительнойОперации().Создано;
		Запись.НомерПопытки          = 0;
		Запись.Наименование          = Строка(КлючЗначение.Ключ);
		Запись.ДатаСоздания          = ТекущаяДатаСеанса;
		Запись.ИмяПользователя       = ИмяПользователя;
		Запись.ПараметрыПотока       = Новый ХранилищеЗначения(КлючЗначение.Значение);
		
	КонецЦикла;
	
	ИдентификаторПотока = ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор();
	Если ОбновленныеПараметрыОперации = Неопределено Тогда
		Запись = НаборЗаписей.Добавить();
		Запись.ИдентификаторПроцесса = ИдентификаторПроцесса;
		Запись.ИдентификаторПотока   = ИдентификаторПотока;
		Запись.НомерПопытки          = 0;
		Запись.ДатаСоздания          = ТекущаяДатаСеанса;
		Запись.ИмяПользователя       = ИмяПользователя;
		НаборЗаписей.Записать();
	Иначе
		Блокировка = Новый БлокировкаДанных;
		ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ДлительныеОперации");
		ЭлементБлокировки.УстановитьЗначение("ИдентификаторПроцесса", ИдентификаторПроцесса);
		ЭлементБлокировки.УстановитьЗначение("ИдентификаторПотока", ИдентификаторПотока);
		НачатьТранзакцию();
		Попытка
			Блокировка.Заблокировать();
			НаборИзОднойЗаписи = РегистрыСведений.ДлительныеОперации.СоздатьНаборЗаписей();
			НаборИзОднойЗаписи.Отбор.ИдентификаторПроцесса.Установить(ИдентификаторПроцесса);
			НаборИзОднойЗаписи.Отбор.ИдентификаторПотока.Установить(ИдентификаторПотока);
			НаборИзОднойЗаписи.Прочитать();
			Если НаборИзОднойЗаписи.Количество() <> 1 Тогда
				ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
					НСтр("ru = 'Не удалось найти запись управляющего потока многопоточной длительной операции %1'"),
					Строка(ИдентификаторПроцесса));
				ВызватьИсключение ТекстОшибки;
			КонецЕсли;
			ТекущийКэш = ОбновленныеПараметрыОперации.ПараметрыМетода.Контекст.Кэш;
			ОбновленныеПараметрыОперации.ПараметрыМетода.Контекст.Кэш = Неопределено;
			НаборИзОднойЗаписи[0].ПараметрыВыполнения = Новый ХранилищеЗначения(ОбновленныеПараметрыОперации);
			ОбновленныеПараметрыОперации.ПараметрыМетода.Контекст.Кэш = ТекущийКэш;
			НаборИзОднойЗаписи.Записать();
			НаборЗаписей.Записать(Ложь);
			ЗафиксироватьТранзакцию();
		Исключение
			ОтменитьТранзакцию();
			ВызватьИсключение;
		КонецПопытки;
	КонецЕсли;
	
КонецПроцедуры

Процедура ПроверитьВозможностьЗапускаМногопоточнойДлительнойОперации(ПараметрыВыполнения, НаборПараметров)
	
	Если НаборПараметров <> Неопределено
	   И ТипЗнч(НаборПараметров) <> Тип("Соответствие")
	   И ТипЗнч(НаборПараметров) <> Тип("Структура") Тогда
		ВызватьИсключение НСтр("ru ='Передан неверный тип набора параметров'");
	КонецЕсли;
	
	Если ОбщегоНазначения.РазделениеВключено() И Не ОбщегоНазначения.ДоступноИспользованиеРазделенныхДанных() Тогда
		ВызватьИсключение НСтр("ru ='Многопоточные длительные операции в неразделенном сеансе не поддерживаются.'");
	КонецЕсли;
	
КонецПроцедуры

// Возвращаемое значение:
//  Структура:
//   * Создано     - Строка
//   * Выполняется - Строка
//   * Выполнено   - Строка
//   * Ошибка      - Строка
//   * Отменено    - Строка
//
Функция СтатусДлительнойОперации()
	
	Результат = Новый Структура();
	Результат.Вставить("Создано",     "");
	Результат.Вставить("Выполняется", "Выполняется");
	Результат.Вставить("Выполнено",   "Выполнено");
	Результат.Вставить("Ошибка",      "Ошибка");
	Результат.Вставить("Отменено",    "Отменено");
	
	Возврат Результат;
	
КонецФункции

Функция КоличествоПопыток()
	Возврат 3;
КонецФункции

Функция ВыполненПерезапускУправляющегоПотока(ИдентификаторЗадания, Задание)
	
	Если Задание <> Неопределено
	   И Задание.Состояние <> СостояниеФоновогоЗадания.ЗавершеноАварийно
	 Или Не ОбщегоНазначения.ДоступноИспользованиеРазделенныхДанных() Тогда
		Возврат Ложь;
	КонецЕсли;
	
	УстановитьОтключениеБезопасногоРежима(Истина);
	УстановитьПривилегированныйРежим(Истина);
	
	ПустойИдентификатор = ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор();
	
	Запрос = Новый Запрос;
	Запрос.УстановитьПараметр("ИдентификаторЗадания", ИдентификаторЗадания);
	Запрос.УстановитьПараметр("ИдентификаторПотока", ПустойИдентификатор);
	Запрос.Текст =
	"ВЫБРАТЬ
	|	ДлительныеОперации.ИдентификаторПроцесса КАК ИдентификаторПроцесса,
	|	ДлительныеОперации.ИдентификаторПотока КАК ИдентификаторПотока,
	|	ДлительныеОперации.НомерПопытки КАК НомерПопытки,
	|	ДлительныеОперации.Статус КАК Статус,
	|	ДлительныеОперации.КлючПотока КАК КлючПотока,
	|	ДлительныеОперации.ПараметрыВыполнения КАК ПараметрыВыполнения
	|ИЗ
	|	РегистрСведений.ДлительныеОперации КАК ДлительныеОперации
	|ГДЕ
	|	ДлительныеОперации.ИдентификаторПотока = &ИдентификаторПотока
	|	И ДлительныеОперации.ИдентификаторЗадания = &ИдентификаторЗадания";
	
	РезультатЗапроса = Запрос.Выполнить();
	Если РезультатЗапроса.Пустой() Тогда
		Возврат Ложь;
	КонецЕсли;
	
	Поток = РезультатЗапроса.Выгрузить()[0];
	
	Если Поток.НомерПопытки >= КоличествоПопыток() Тогда
		Возврат Ложь;
	КонецЕсли;
	
	Попытка
		ПараметрыОперации = ПараметрыМногопоточнойОперации(Поток.ИдентификаторПроцесса,
			Поток.ПараметрыВыполнения.Получить());
		
		ПараметрыВыполнения = ПараметрыОперации.ПараметрыВыполнения;
		ПараметрыВыполнения.ОжидатьЗавершение = 0;
		// При перезапуске фонового задания обязателен запуск нового фонового задания,
		// поэтому запуск всегда в фоне (очередь заданий в файловом варианте).
		ПараметрыВыполнения.ЗапуститьВФоне = Истина;
		ПараметрыВыполнения.Вставить("ЭтоПерезапускУправляющегоПотока");
		
		РезультатЗапуска = ВыполнитьФункцию(ПараметрыВыполнения,
			ИмяМетодаМногопоточногоПроцесса(), ПараметрыОперации);
		
		Если Не ЗначениеЗаполнено(РезультатЗапуска.ИдентификаторЗадания) Тогда
			ТекстОшибки = НСтр("ru = 'Получен пустой идентификатор фонового задания'");
			ВызватьИсключение ТекстОшибки;
		КонецЕсли;
		НовоеЗадание = НайтиЗаданиеПоИдентификатору(РезультатЗапуска.ИдентификаторЗадания);
		Если НовоеЗадание = Неопределено Тогда
			ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
				НСтр("ru = 'Не найдено новое фоновое задание по идентификатору %1'"),
				РезультатЗапуска.ИдентификаторЗадания);
			ВызватьИсключение ТекстОшибки;
		КонецЕсли;
		Задание = НовоеЗадание;
		
		Свойства = Новый Структура(ПараметрыСеанса.ДлительныеОперации);
		Перезапущенные = Новый Соответствие(Свойства.Перезапущенные);
		Перезапущенные.Вставить(ИдентификаторЗадания, РезультатЗапуска.ИдентификаторЗадания);
		Свойства.Перезапущенные = Новый ФиксированноеСоответствие(Перезапущенные);
		ПараметрыСеанса.ДлительныеОперации = Новый ФиксированнаяСтруктура(Свойства);
		
		Поток.КлючПотока = Новый ХранилищеЗначения(РезультатЗапуска.ИдентификаторЗадания);
		РезультатЗапуска.ИдентификаторЗадания = ИдентификаторЗадания;
		ОбновитьСведенияОбПотоке(Поток, РезультатЗапуска);
	Исключение
		ТекстОшибки = СтроковыеФункцииКлиентСервер.ПодставитьПараметрыВСтроку(
			НСтр("ru = 'При попытке перезапуска фонового задания %1
			           |управляющего потока %2 возникла ошибка:
			           |
			           |%3'"),
			Строка(ИдентификаторЗадания),
			Строка(Поток.ИдентификаторПроцесса),
			ОбработкаОшибок.ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
		ЗаписатьОшибку(ТекстОшибки);
		Возврат Ложь;
	КонецПопытки;
	
	Возврат Истина;
	
КонецФункции

Процедура УдалитьДанныеОПотоках(ИдентификаторПроцесса)
	
	УстановитьПривилегированныйРежим(Истина);
	
	НаборЗаписей = РегистрыСведений.ДлительныеОперации.СоздатьНаборЗаписей();
	НаборЗаписей.Отбор.ИдентификаторПроцесса.Установить(ИдентификаторПроцесса);
	
	НаборЗаписей.Записать();
	
КонецПроцедуры

Процедура ЗапланироватьЗапускПотоковДлительныхОпераций(РезультатЗапуска, ПараметрыОперации)
	
	УстановитьПривилегированныйРежим(Истина);
	
	ИдентификаторПотока   = ОбщегоНазначенияКлиентСервер.ПустойУникальныйИдентификатор();
	ИдентификаторПроцесса = ПараметрыОперации.ИдентификаторПроцесса;
	
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.ДлительныеОперации"); 
	ЭлементБлокировки.УстановитьЗначение("ИдентификаторПроцесса", ИдентификаторПроцесса);
	ЭлементБлокировки.УстановитьЗначение("ИдентификаторПотока", ИдентификаторПотока);
	
	НаборЗаписей = РегистрыСведений.ДлительныеОперации.СоздатьНаборЗаписей();
	НаборЗаписей.Отбор.ИдентификаторПроцесса.Установить(ИдентификаторПроцесса);
	НаборЗаписей.Отбор.ИдентификаторПотока.Установить(ИдентификаторПотока);
	
	НачатьТранзакцию();
	Попытка
		Блокировка.Заблокировать();
		НаборЗаписей.Прочитать();
		
		Если НаборЗаписей.Количество() > 0 Тогда
			Запись = НаборЗаписей[0];
		Иначе
			Запись                       = НаборЗаписей.Добавить();
			Запись.ИдентификаторПроцесса = ИдентификаторПроцесса;
			Запись.ИдентификаторПотока   = ИдентификаторПотока;
			Запись.НомерПопытки          = 0;
			Запись.ДатаСоздания          = ТекущаяДатаСеанса();
			Запись.ИмяПользователя       = ИмяПользователя();
		КонецЕсли;
		Запись.АдресРезультата       = РезультатЗапуска.АдресРезультата;
		Запись.ИдентификаторЗадания  = РезультатЗапуска.ИдентификаторЗадания;
		Запись.Статус                = РезультатЗапуска.Статус;
		Запись.ПараметрыВыполнения   = Новый ХранилищеЗначения(ПараметрыОперации);
		
		НаборЗаписей.Записать();
		
		ЗафиксироватьТранзакцию();
	Исключение
		ОтменитьТранзакцию();
		ВызватьИсключение;
	КонецПопытки;
	
КонецПроцедуры

////////////////////////////////////////////////////////////////////////////////
// Журнал регистрации

Процедура ЗаписатьОшибку(Знач Текст)
	
	ЖурналРегистрации.ДобавитьСообщениеДляЖурналаРегистрации(СобытиеЖурналаРегистрации(), УровеньЖурналаРегистрации.Ошибка,,, Текст);
	
КонецПроцедуры

// Возвращает строковую константу для формирования сообщений журнала регистрации.
//
// Возвращаемое значение:
//   Строка
//
Функция СобытиеЖурналаРегистрации() Экспорт
	
	Возврат НСтр("ru = 'Многопоточные длительные операции'", ОбщегоНазначения.КодОсновногоЯзыка());
	
КонецФункции

#КонецОбласти